From 81bab58765e9c15cf0364f10072c60c399166bf2 Mon Sep 17 00:00:00 2001 From: 100gle Date: Wed, 21 Oct 2026 11:31:56 +0800 Subject: [PATCH 01/28] docs: restructure the documentation tree Navigation now follows how you use Bub instead of which files exist: Get started -> Concepts -> Use Bub App -> Build your own Bub Agent -> Integrations -> Reference. The application comes first because the SDK track ends by reusing it through BubAgent. - move 31 pages per locale into app/, sdk/, and integrations/ - merge the two app hubs into a single "Use the Bub app" overview - merge the first-skill and first-plugin tutorials into the Skills and Plugins guides, keeping their prose - badge integration pages with App, Both, or Experimental - add 32 redirects for the old URLs in both locales - rewrite 336 internal links plus the README and landing-page links Only four pages have edited prose: the three merged pages and the Get started index, whose two sections are swapped. The other 68 page files are byte-identical after normalizing their internal URLs. --- README.md | 6 +- website/astro.config.mjs | 161 +++++++++-------- .../channels/cli.mdx => app/chat.mdx} | 4 +- .../docs/docs/{operate => app}/configure.mdx | 8 +- .../docs/docs/{operate => app}/deploy.mdx | 8 +- .../first-turn.mdx => app/first-session.mdx} | 10 +- .../channels/index.mdx => app/gateway.mdx} | 12 +- .../docs/docs/{build => app}/hooks.mdx | 10 +- website/src/content/docs/docs/app/index.mdx | 42 +++++ .../language-extensions.mdx | 3 +- .../distribution.mdx => app/package.mdx} | 6 +- website/src/content/docs/docs/app/plugins.mdx | 162 ++++++++++++++++++ .../docs/docs/{build => app}/skills.mdx | 55 +++++- .../{operate/channels => app}/telegram.mdx | 8 +- .../{build/app-tools.mdx => app/tools.mdx} | 16 +- website/src/content/docs/docs/build/index.mdx | 33 ---- .../src/content/docs/docs/build/plugins.mdx | 105 ------------ .../concepts/{philosophy.mdx => design.mdx} | 6 +- .../src/content/docs/docs/concepts/events.mdx | 10 +- .../{surfaces.mdx => extension-points.mdx} | 18 +- .../src/content/docs/docs/concepts/index.mdx | 12 +- .../{tape-and-context.mdx => tape.mdx} | 6 +- .../concepts/{turn-pipeline.mdx => turn.mdx} | 2 +- .../docs/getting-started/first-plugin.mdx | 94 ---------- .../docs/docs/getting-started/first-skill.mdx | 53 ------ .../docs/docs/getting-started/index.mdx | 12 +- .../docs/docs/getting-started/install.mdx | 4 +- .../acp-server.mdx => integrations/acp.mdx} | 7 +- .../agent-plugins.mdx | 11 +- .../content/docs/docs/integrations/index.mdx | 26 +++ .../local-models.mdx} | 1 + .../docs/{tutorials => integrations}/mcp.mdx | 11 +- .../observability.mdx | 5 +- .../storage.mdx} | 9 +- .../src/content/docs/docs/operate/index.mdx | 21 --- .../src/content/docs/docs/reference/cli.mdx | 4 +- .../src/content/docs/docs/reference/hooks.mdx | 4 +- .../src/content/docs/docs/reference/index.mdx | 10 +- .../content/docs/docs/reference/settings.mdx | 6 +- .../src/content/docs/docs/reference/types.mdx | 4 +- .../content/docs/docs/reference/versions.mdx | 8 +- .../reuse-plugins.mdx => sdk/app-plugins.mdx} | 12 +- .../docs/docs/{build/sdk.md => sdk/index.md} | 12 +- .../docs/docs/{build => sdk}/tools.mdx | 14 +- .../fastapi.mdx => sdk/web-service.mdx} | 8 +- .../src/content/docs/docs/tutorials/index.mdx | 26 --- .../channels/cli.mdx => app/chat.mdx} | 4 +- .../zh-cn/docs/{operate => app}/configure.mdx | 8 +- .../zh-cn/docs/{operate => app}/deploy.mdx | 8 +- .../first-turn.mdx => app/first-session.mdx} | 10 +- .../channels/index.mdx => app/gateway.mdx} | 12 +- .../docs/zh-cn/docs/{build => app}/hooks.mdx | 10 +- .../src/content/docs/zh-cn/docs/app/index.mdx | 42 +++++ .../language-extensions.mdx | 3 +- .../distribution.mdx => app/package.mdx} | 6 +- .../content/docs/zh-cn/docs/app/plugins.mdx | 162 ++++++++++++++++++ .../docs/zh-cn/docs/{build => app}/skills.mdx | 55 +++++- .../{operate/channels => app}/telegram.mdx | 8 +- .../{build/app-tools.mdx => app/tools.mdx} | 16 +- .../content/docs/zh-cn/docs/build/index.mdx | 33 ---- .../content/docs/zh-cn/docs/build/plugins.mdx | 105 ------------ .../concepts/{philosophy.mdx => design.mdx} | 6 +- .../docs/zh-cn/docs/concepts/events.mdx | 10 +- .../{surfaces.mdx => extension-points.mdx} | 18 +- .../docs/zh-cn/docs/concepts/index.mdx | 12 +- .../{tape-and-context.mdx => tape.mdx} | 6 +- .../concepts/{turn-pipeline.mdx => turn.mdx} | 2 +- .../docs/getting-started/first-plugin.mdx | 94 ---------- .../docs/getting-started/first-skill.mdx | 53 ------ .../docs/zh-cn/docs/getting-started/index.mdx | 12 +- .../zh-cn/docs/getting-started/install.mdx | 4 +- .../acp-server.mdx => integrations/acp.mdx} | 7 +- .../agent-plugins.mdx | 11 +- .../docs/zh-cn/docs/integrations/index.mdx | 26 +++ .../local-models.mdx} | 1 + .../docs/{tutorials => integrations}/mcp.mdx | 11 +- .../observability.mdx | 5 +- .../storage.mdx} | 9 +- .../content/docs/zh-cn/docs/operate/index.mdx | 21 --- .../content/docs/zh-cn/docs/reference/cli.mdx | 4 +- .../docs/zh-cn/docs/reference/hooks.mdx | 4 +- .../docs/zh-cn/docs/reference/index.mdx | 10 +- .../docs/zh-cn/docs/reference/settings.mdx | 6 +- .../docs/zh-cn/docs/reference/types.mdx | 4 +- .../docs/zh-cn/docs/reference/versions.mdx | 8 +- .../reuse-plugins.mdx => sdk/app-plugins.mdx} | 12 +- .../zh-cn/docs/{build/sdk.md => sdk/index.md} | 12 +- .../docs/zh-cn/docs/{build => sdk}/tools.mdx | 14 +- .../fastapi.mdx => sdk/web-service.mdx} | 8 +- .../docs/zh-cn/docs/tutorials/index.mdx | 26 --- 90 files changed, 938 insertions(+), 1025 deletions(-) rename website/src/content/docs/docs/{operate/channels/cli.mdx => app/chat.mdx} (89%) rename website/src/content/docs/docs/{operate => app}/configure.mdx (94%) rename website/src/content/docs/docs/{operate => app}/deploy.mdx (94%) rename website/src/content/docs/docs/{getting-started/first-turn.mdx => app/first-session.mdx} (72%) rename website/src/content/docs/docs/{operate/channels/index.mdx => app/gateway.mdx} (89%) rename website/src/content/docs/docs/{build => app}/hooks.mdx (90%) create mode 100644 website/src/content/docs/docs/app/index.mdx rename website/src/content/docs/docs/{tutorials => app}/language-extensions.mdx (97%) rename website/src/content/docs/docs/{build/distribution.mdx => app/package.mdx} (92%) create mode 100644 website/src/content/docs/docs/app/plugins.mdx rename website/src/content/docs/docs/{build => app}/skills.mdx (60%) rename website/src/content/docs/docs/{operate/channels => app}/telegram.mdx (94%) rename website/src/content/docs/docs/{build/app-tools.mdx => app/tools.mdx} (82%) delete mode 100644 website/src/content/docs/docs/build/index.mdx delete mode 100644 website/src/content/docs/docs/build/plugins.mdx rename website/src/content/docs/docs/concepts/{philosophy.mdx => design.mdx} (90%) rename website/src/content/docs/docs/concepts/{surfaces.mdx => extension-points.mdx} (58%) rename website/src/content/docs/docs/concepts/{tape-and-context.mdx => tape.mdx} (94%) rename website/src/content/docs/docs/concepts/{turn-pipeline.mdx => turn.mdx} (93%) delete mode 100644 website/src/content/docs/docs/getting-started/first-plugin.mdx delete mode 100644 website/src/content/docs/docs/getting-started/first-skill.mdx rename website/src/content/docs/docs/{tutorials/acp-server.mdx => integrations/acp.mdx} (93%) rename website/src/content/docs/docs/{tutorials => integrations}/agent-plugins.mdx (95%) create mode 100644 website/src/content/docs/docs/integrations/index.mdx rename website/src/content/docs/docs/{tutorials/local-llama-cpp.mdx => integrations/local-models.mdx} (99%) rename website/src/content/docs/docs/{tutorials => integrations}/mcp.mdx (91%) rename website/src/content/docs/docs/{tutorials => integrations}/observability.mdx (97%) rename website/src/content/docs/docs/{tutorials/tapestore-sqlalchemy.mdx => integrations/storage.mdx} (88%) delete mode 100644 website/src/content/docs/docs/operate/index.mdx rename website/src/content/docs/docs/{build/reuse-plugins.mdx => sdk/app-plugins.mdx} (95%) rename website/src/content/docs/docs/{build/sdk.md => sdk/index.md} (93%) rename website/src/content/docs/docs/{build => sdk}/tools.mdx (92%) rename website/src/content/docs/docs/{tutorials/fastapi.mdx => sdk/web-service.mdx} (86%) delete mode 100644 website/src/content/docs/docs/tutorials/index.mdx rename website/src/content/docs/zh-cn/docs/{operate/channels/cli.mdx => app/chat.mdx} (89%) rename website/src/content/docs/zh-cn/docs/{operate => app}/configure.mdx (95%) rename website/src/content/docs/zh-cn/docs/{operate => app}/deploy.mdx (94%) rename website/src/content/docs/zh-cn/docs/{getting-started/first-turn.mdx => app/first-session.mdx} (72%) rename website/src/content/docs/zh-cn/docs/{operate/channels/index.mdx => app/gateway.mdx} (87%) rename website/src/content/docs/zh-cn/docs/{build => app}/hooks.mdx (89%) create mode 100644 website/src/content/docs/zh-cn/docs/app/index.mdx rename website/src/content/docs/zh-cn/docs/{tutorials => app}/language-extensions.mdx (97%) rename website/src/content/docs/zh-cn/docs/{build/distribution.mdx => app/package.mdx} (90%) create mode 100644 website/src/content/docs/zh-cn/docs/app/plugins.mdx rename website/src/content/docs/zh-cn/docs/{build => app}/skills.mdx (62%) rename website/src/content/docs/zh-cn/docs/{operate/channels => app}/telegram.mdx (92%) rename website/src/content/docs/zh-cn/docs/{build/app-tools.mdx => app/tools.mdx} (81%) delete mode 100644 website/src/content/docs/zh-cn/docs/build/index.mdx delete mode 100644 website/src/content/docs/zh-cn/docs/build/plugins.mdx rename website/src/content/docs/zh-cn/docs/concepts/{philosophy.mdx => design.mdx} (90%) rename website/src/content/docs/zh-cn/docs/concepts/{surfaces.mdx => extension-points.mdx} (56%) rename website/src/content/docs/zh-cn/docs/concepts/{tape-and-context.mdx => tape.mdx} (94%) rename website/src/content/docs/zh-cn/docs/concepts/{turn-pipeline.mdx => turn.mdx} (93%) delete mode 100644 website/src/content/docs/zh-cn/docs/getting-started/first-plugin.mdx delete mode 100644 website/src/content/docs/zh-cn/docs/getting-started/first-skill.mdx rename website/src/content/docs/zh-cn/docs/{tutorials/acp-server.mdx => integrations/acp.mdx} (93%) rename website/src/content/docs/zh-cn/docs/{tutorials => integrations}/agent-plugins.mdx (94%) create mode 100644 website/src/content/docs/zh-cn/docs/integrations/index.mdx rename website/src/content/docs/zh-cn/docs/{tutorials/local-llama-cpp.mdx => integrations/local-models.mdx} (98%) rename website/src/content/docs/zh-cn/docs/{tutorials => integrations}/mcp.mdx (90%) rename website/src/content/docs/zh-cn/docs/{tutorials => integrations}/observability.mdx (97%) rename website/src/content/docs/zh-cn/docs/{tutorials/tapestore-sqlalchemy.mdx => integrations/storage.mdx} (87%) delete mode 100644 website/src/content/docs/zh-cn/docs/operate/index.mdx rename website/src/content/docs/zh-cn/docs/{build/reuse-plugins.mdx => sdk/app-plugins.mdx} (95%) rename website/src/content/docs/zh-cn/docs/{build/sdk.md => sdk/index.md} (93%) rename website/src/content/docs/zh-cn/docs/{build => sdk}/tools.mdx (91%) rename website/src/content/docs/zh-cn/docs/{tutorials/fastapi.mdx => sdk/web-service.mdx} (86%) delete mode 100644 website/src/content/docs/zh-cn/docs/tutorials/index.mdx diff --git a/README.md b/README.md index 7874e2c07..ff3b856ea 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ In a Python 3.12+ project: uv add bub ``` -Run your first agent with the [SDK guide](https://bub.build/docs/build/sdk/), then [serve it with FastAPI](https://bub.build/docs/tutorials/fastapi/) when you need an HTTP endpoint. +Run your first agent with the [SDK guide](https://bub.build/docs/sdk/), then [serve it with FastAPI](https://bub.build/docs/sdk/web-service/) when you need an HTTP endpoint. -You can also embed `BubAgent` to reuse the application's settings and plugin hooks in your service. This uses `bub[app]`; [Reuse application plugins](https://bub.build/docs/build/reuse-plugins/) explains the integration choices and their requirements. +You can also embed `BubAgent` to reuse the application's settings and plugin hooks in your service. This uses `bub[app]`; [Reuse application plugins](https://bub.build/docs/sdk/app-plugins/) explains the integration choices and their requirements. ## Customize the Bub app @@ -51,7 +51,7 @@ Follow the prompts to choose plugins and configure your model, then start a conv bub chat ``` -[Run your first conversation](https://bub.build/docs/getting-started/first-turn/) walks through a task in your workspace. [Customize the Bub app](https://bub.build/docs/build/) covers tools, plugins, and packaging. For `uv tool install` and other setup options, see [Install Bub](https://bub.build/docs/getting-started/install/#app-install). +[Run your first conversation](https://bub.build/docs/app/first-session/) walks through a task in your workspace. [Customize the Bub app](https://bub.build/docs/app/) covers tools, plugins, and packaging. For `uv tool install` and other setup options, see [Install Bub](https://bub.build/docs/getting-started/install/#app-install). ## Documentation diff --git a/website/astro.config.mjs b/website/astro.config.mjs index cd99e3440..2be6e74c0 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -27,6 +27,41 @@ export default defineConfig({ prerenderEnvironment: 'node', }), site: process.env.SITE_URL ?? 'https://bub.build', + // Documentation moved in the 0.6 documentation restructure. Keep old URLs working. + redirects: { + '/docs': '/docs/getting-started/', + '/docs/getting-started/first-turn': '/docs/app/first-session/', + '/docs/getting-started/first-skill': '/docs/app/skills/', + '/docs/getting-started/first-plugin': '/docs/app/plugins/', + '/docs/build': '/docs/app/', + '/docs/build/sdk': '/docs/sdk/', + '/docs/build/tools': '/docs/sdk/tools/', + '/docs/build/app-tools': '/docs/app/tools/', + '/docs/build/skills': '/docs/app/skills/', + '/docs/build/plugins': '/docs/app/plugins/', + '/docs/build/hooks': '/docs/app/hooks/', + '/docs/build/reuse-plugins': '/docs/sdk/app-plugins/', + '/docs/build/distribution': '/docs/app/package/', + '/docs/concepts/philosophy': '/docs/concepts/design/', + '/docs/concepts/surfaces': '/docs/concepts/extension-points/', + '/docs/concepts/tape-and-context': '/docs/concepts/tape/', + '/docs/concepts/turn-pipeline': '/docs/concepts/turn/', + '/docs/operate': '/docs/app/', + '/docs/operate/configure': '/docs/app/configure/', + '/docs/operate/channels': '/docs/app/gateway/', + '/docs/operate/channels/cli': '/docs/app/chat/', + '/docs/operate/channels/telegram': '/docs/app/telegram/', + '/docs/operate/deploy': '/docs/app/deploy/', + '/docs/tutorials': '/docs/integrations/', + '/docs/tutorials/mcp': '/docs/integrations/mcp/', + '/docs/tutorials/acp-server': '/docs/integrations/acp/', + '/docs/tutorials/agent-plugins': '/docs/integrations/agent-plugins/', + '/docs/tutorials/fastapi': '/docs/sdk/web-service/', + '/docs/tutorials/language-extensions': '/docs/app/language-extensions/', + '/docs/tutorials/local-llama-cpp': '/docs/integrations/local-models/', + '/docs/tutorials/tapestore-sqlalchemy': '/docs/integrations/storage/', + '/docs/tutorials/observability': '/docs/integrations/observability/', + }, env: { schema: { SITE_URL: envField.string({ @@ -111,101 +146,87 @@ export default defineConfig({ social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/bubbuild/bub' }], sidebar: [ { - label: 'Start here', - translations: { 'zh-CN': '从这里开始' }, + label: 'Get started', + translations: { 'zh-CN': '快速开始' }, items: ['docs/getting-started', 'docs/getting-started/install'], }, { - label: 'Build your own agent', - translations: { 'zh-CN': '构建自己的 Agent' }, - items: ['docs/build/sdk', 'docs/build/reuse-plugins', 'docs/tutorials/fastapi'], + label: 'Concepts', + translations: { 'zh-CN': '核心概念' }, + items: [ + 'docs/concepts', + 'docs/concepts/tape', + 'docs/concepts/turn', + 'docs/concepts/events', + 'docs/concepts/extension-points', + 'docs/concepts/design', + ], }, { - label: 'Customize the Bub app', - translations: { 'zh-CN': '定制 Bub 应用' }, + label: 'Use Bub App', + translations: { 'zh-CN': '使用 Bub 应用' }, items: [ - 'docs/build', - 'docs/getting-started/first-turn', - 'docs/concepts/turn-pipeline', - { - label: 'Extend the app', - translations: { 'zh-CN': '扩展应用' }, - collapsed: true, - items: [ - 'docs/build/app-tools', - 'docs/getting-started/first-skill', - 'docs/build/skills', - 'docs/getting-started/first-plugin', - 'docs/build/plugins', - 'docs/build/hooks', - 'docs/build/distribution', - ], - }, + 'docs/app', { - label: 'Configure and run', - translations: { 'zh-CN': '配置与运行' }, - collapsed: true, + label: 'Run it', + translations: { 'zh-CN': '运行与部署' }, + collapsed: false, items: [ - 'docs/operate', - 'docs/operate/configure', - 'docs/operate/channels', - 'docs/operate/channels/cli', - 'docs/operate/channels/telegram', - 'docs/operate/deploy', + 'docs/app/first-session', + 'docs/app/configure', + 'docs/app/chat', + 'docs/app/gateway', + 'docs/app/telegram', + 'docs/app/deploy', + 'docs/app/package', ], }, { - label: 'Integrations', - translations: { 'zh-CN': '接入集成' }, + label: 'Extend it', + translations: { 'zh-CN': '扩展它' }, collapsed: true, items: [ - { - slug: 'docs/tutorials', - label: 'Integration index', - translations: { 'zh-CN': '集成索引' }, - }, - 'docs/tutorials/mcp', - 'docs/tutorials/agent-plugins', - 'docs/tutorials/language-extensions', - 'docs/tutorials/acp-server', - 'docs/tutorials/tapestore-sqlalchemy', - 'docs/tutorials/observability', - 'docs/tutorials/local-llama-cpp', + 'docs/app/tools', + 'docs/app/skills', + 'docs/app/plugins', + 'docs/app/hooks', + 'docs/app/language-extensions', ], }, ], }, { - label: 'Shared concepts and capabilities', - translations: { 'zh-CN': '公共概念与能力' }, + label: 'Build your own Bub Agent', + translations: { 'zh-CN': '构建你的 Bub Agent' }, items: [ - 'docs/concepts', - 'docs/build/tools', - 'docs/concepts/tape-and-context', - 'docs/concepts/events', - 'docs/concepts/surfaces', - 'docs/concepts/philosophy', + 'docs/sdk', + 'docs/sdk/tools', + 'docs/sdk/web-service', + 'docs/sdk/app-plugins', + ], + }, + { + label: 'Integrations', + translations: { 'zh-CN': '集成' }, + items: [ + 'docs/integrations', + 'docs/integrations/local-models', + 'docs/integrations/storage', + 'docs/integrations/observability', + 'docs/integrations/mcp', + 'docs/integrations/agent-plugins', + 'docs/integrations/acp', ], }, { label: 'Reference', translations: { 'zh-CN': '参考' }, items: [ - { - slug: 'docs/reference', - label: 'Agent and app APIs', - translations: { 'zh-CN': 'Agent 与应用接口' }, - }, - { - label: 'Application reference', - translations: { 'zh-CN': '应用参考' }, - items: [ - 'docs/reference/hooks', - 'docs/reference/cli', - 'docs/reference/settings', - 'docs/reference/types', - ], - }, + 'docs/reference', + 'docs/reference/cli', + 'docs/reference/settings', + 'docs/reference/types', + 'docs/reference/hooks', 'docs/reference/versions', ], }, diff --git a/website/src/content/docs/docs/operate/channels/cli.mdx b/website/src/content/docs/docs/app/chat.mdx similarity index 89% rename from website/src/content/docs/docs/operate/channels/cli.mdx rename to website/src/content/docs/docs/app/chat.mdx index 9b63bf4f0..472e20b6e 100644 --- a/website/src/content/docs/docs/operate/channels/cli.mdx +++ b/website/src/content/docs/docs/app/chat.mdx @@ -3,7 +3,7 @@ title: Use the terminal description: Continue a conversation with bub chat or run one task from the shell with bub run. --- -The Bub app has two terminal entry points: `bub chat` for a conversation and `bub run` for one message. [Configure a model](/docs/operate/configure/) before calling it. +The Bub app has two terminal entry points: `bub chat` for a conversation and `bub run` for one message. [Configure a model](/docs/app/configure/) before calling it. ## Continue a conversation @@ -41,4 +41,4 @@ bub run ",fs.read path=README.md" You can enter these commands in an interactive conversation too. Ctrl-X toggles shell mode, which adds the command prefix to your input. -The CLI owns the foreground terminal, so the gateway excludes it by default. See [Channels](/docs/operate/channels/) for gateway selection and batching, or the [CLI reference](/docs/reference/cli/) for every option. +The CLI owns the foreground terminal, so the gateway excludes it by default. See [Channels](/docs/app/gateway/) for gateway selection and batching, or the [CLI reference](/docs/reference/cli/) for every option. diff --git a/website/src/content/docs/docs/operate/configure.mdx b/website/src/content/docs/docs/app/configure.mdx similarity index 94% rename from website/src/content/docs/docs/operate/configure.mdx rename to website/src/content/docs/docs/app/configure.mdx index f7af4fda5..d35c16a3d 100644 --- a/website/src/content/docs/docs/operate/configure.mdx +++ b/website/src/content/docs/docs/app/configure.mdx @@ -5,7 +5,7 @@ sidebar: order: 1 --- -Choose the model the Bub application calls and the channels it connects to. These settings belong to the application. When you construct `core.Agent` directly, pass its model and components in Python; see [Build your own agent](/docs/build/sdk/). +Choose the model the Bub application calls and the channels it connects to. These settings belong to the application. When you construct `core.Agent` directly, pass its model and components in Python; see [Build your own agent](/docs/sdk/). Bub reads its own configuration from these places, in order of precedence (highest first): @@ -79,7 +79,7 @@ BUB_TELEGRAM_ALLOW_CHATS=-1001234567890 BUB_TELEGRAM_PROXY=http://127.0.0.1:7890 # optional ``` -For the message-handling and access-control behavior these variables drive, see [Telegram](/docs/operate/channels/telegram/). +For the message-handling and access-control behavior these variables drive, see [Telegram](/docs/app/telegram/). ## 5. Verify @@ -93,6 +93,6 @@ The command prints a routing label (`[:]`) followed by the res ## Next steps -- [Channels](/docs/operate/channels/): choose which surfaces Bub listens on. +- [Channels](/docs/app/gateway/): choose which surfaces Bub listens on. - [Settings reference](/docs/reference/settings/): full env-var table. -- [Deploy](/docs/operate/deploy/): package this config into a container. +- [Deploy](/docs/app/deploy/): package this config into a container. diff --git a/website/src/content/docs/docs/operate/deploy.mdx b/website/src/content/docs/docs/app/deploy.mdx similarity index 94% rename from website/src/content/docs/docs/operate/deploy.mdx rename to website/src/content/docs/docs/app/deploy.mdx index ab17f0819..d1b383831 100644 --- a/website/src/content/docs/docs/operate/deploy.mdx +++ b/website/src/content/docs/docs/app/deploy.mdx @@ -12,8 +12,8 @@ The repository ships three files that drive the canonical deployment: [`Dockerfi ## Before you begin - Docker (or compatible runtime) and `docker compose` are installed. -- You have a `.env` with at minimum `BUB_MODEL` and either `BUB_API_KEY` or a provider-specific key: see [Configure](/docs/operate/configure/). -- For a Telegram deployment, `BUB_TELEGRAM_TOKEN` and an allowlist are in `.env`: see [Telegram](/docs/operate/channels/telegram/). +- You have a `.env` with at minimum `BUB_MODEL` and either `BUB_API_KEY` or a provider-specific key: see [Configure](/docs/app/configure/). +- For a Telegram deployment, `BUB_TELEGRAM_TOKEN` and an allowlist are in `.env`: see [Telegram](/docs/app/telegram/). ## 1. Prepare the env file @@ -139,6 +139,6 @@ Both commands operate on the managed project at `BUB_PROJECT` (default `bub.home ## Next steps -- [Configure](/docs/operate/configure/): env-var precedence and provider settings. -- [Telegram](/docs/operate/channels/telegram/): bot token and allowlist behavior. +- [Configure](/docs/app/configure/): env-var precedence and provider settings. +- [Telegram](/docs/app/telegram/): bot token and allowlist behavior. - [CLI reference](/docs/reference/cli/): flags for `bub gateway`, `bub install`, `bub update`. diff --git a/website/src/content/docs/docs/getting-started/first-turn.mdx b/website/src/content/docs/docs/app/first-session.mdx similarity index 72% rename from website/src/content/docs/docs/getting-started/first-turn.mdx rename to website/src/content/docs/docs/app/first-session.mdx index c7db97331..6b4953dc6 100644 --- a/website/src/content/docs/docs/getting-started/first-turn.mdx +++ b/website/src/content/docs/docs/app/first-session.mdx @@ -7,7 +7,7 @@ sidebar: Start with the Bub app, then adapt it to your work. This tutorial gives it a workspace and instructions, checks that commands work, and runs a conversation with a model. -Follow [Install Bub](/docs/getting-started/install/) for the application setup and choose a model in [Configure Bub](/docs/operate/configure/). The model-backed step needs the credentials for that provider. The command check works without them. +Follow [Install Bub](/docs/getting-started/install/) for the application setup and choose a model in [Configure Bub](/docs/app/configure/). The model-backed step needs the credentials for that provider. The command check works without them. ## Give Bub a workspace @@ -54,10 +54,10 @@ Bub passes the message and workspace instructions to its agent, records the conv bub chat ``` -If a command works but a conversation fails, check the model name and credentials in [Configure Bub](/docs/operate/configure/). If a plugin is missing, `bub hooks` shows which hook implementations the application discovered. +If a command works but a conversation fails, check the model name and credentials in [Configure Bub](/docs/app/configure/). If a plugin is missing, `bub hooks` shows which hook implementations the application discovered. ## Make it your own -- Add a [project skill](/docs/getting-started/first-skill/) for a procedure you want Bub to follow. -- Add an [application tool](/docs/build/app-tools/) when it needs to call your code. -- Write a [plugin](/docs/getting-started/first-plugin/) to change application behavior. +- Add a [project skill](/docs/app/skills/) for a procedure you want Bub to follow. +- Add an [application tool](/docs/app/tools/) when it needs to call your code. +- Write a [plugin](/docs/app/plugins/) to change application behavior. diff --git a/website/src/content/docs/docs/operate/channels/index.mdx b/website/src/content/docs/docs/app/gateway.mdx similarity index 89% rename from website/src/content/docs/docs/operate/channels/index.mdx rename to website/src/content/docs/docs/app/gateway.mdx index b675b6ff5..019a2b40c 100644 --- a/website/src/content/docs/docs/operate/channels/index.mdx +++ b/website/src/content/docs/docs/app/gateway.mdx @@ -9,7 +9,7 @@ Channels connect the Bub application to terminals and messaging services. Choose ## Before you begin -- You have run `bub onboard` or written `~/.bub/config.yml` directly. See [Configure](/docs/operate/configure/). +- You have run `bub onboard` or written `~/.bub/config.yml` directly. See [Configure](/docs/app/configure/). - For each non-CLI channel, the channel's credentials are set (e.g. `BUB_TELEGRAM_TOKEN`). ## 1. Choose which channels to enable @@ -78,13 +78,13 @@ Override with `--session-id` on `bub run` or `bub chat` when you need to share s | Channel | Default? | Debounces? | Page | |---|---|---|---| -| `cli` | only via `bub chat` | no | [CLI](/docs/operate/channels/cli/) | -| `telegram` | yes (when `BUB_TELEGRAM_TOKEN` is set) | yes | [Telegram](/docs/operate/channels/telegram/) | +| `cli` | only via `bub chat` | no | [CLI](/docs/app/chat/) | +| `telegram` | yes (when `BUB_TELEGRAM_TOKEN` is set) | yes | [Telegram](/docs/app/telegram/) | -Plugins can register additional channels through the `provide_channels` hook. Use [plugins](/docs/build/plugins/) to add other channel adapters. +Plugins can register additional channels through the `provide_channels` hook. Use [plugins](/docs/app/plugins/) to add other channel adapters. ## Next steps -- [CLI](/docs/operate/channels/cli/): REPL and one-shot turns. -- [Telegram](/docs/operate/channels/telegram/): token, allowlists, group behavior. +- [CLI](/docs/app/chat/): REPL and one-shot turns. +- [Telegram](/docs/app/telegram/): token, allowlists, group behavior. - [Settings reference](/docs/reference/settings/): every channel variable. diff --git a/website/src/content/docs/docs/build/hooks.mdx b/website/src/content/docs/docs/app/hooks.mdx similarity index 90% rename from website/src/content/docs/docs/build/hooks.mdx rename to website/src/content/docs/docs/app/hooks.mdx index 79da92872..727d43465 100644 --- a/website/src/content/docs/docs/build/hooks.mdx +++ b/website/src/content/docs/docs/app/hooks.mdx @@ -7,7 +7,7 @@ sidebar: Choose a hook at the point where you want to change the Bub app. Keep the rest of the application in place: a prompt hook changes the input, a tool decision controls one call, and a store provider changes where conversation history lives. -These examples belong in a [plugin](/docs/build/plugins/). Module-level functions can use a module entry point. For a class, register an instance or a factory that accepts the framework. +These examples belong in a [plugin](/docs/app/plugins/). Module-level functions can use a module entry point. For a class, register an instance or a factory that accepts the framework. The [hooks reference](/docs/reference/hooks/) defines signatures, priority, and lifecycle rules. @@ -94,7 +94,7 @@ def after_tool_call(call, result, state): These hooks observe completed calls, including ordinary failures. They do not report cancellation. For per-chunk output, use the stream interface in your host or channel and follow its [closing rules](/docs/reference/types/#streaming). -To replace the entire model stage, implement either `run_model` or `run_model_stream`. [Your first plugin](/docs/getting-started/first-plugin/) shows a text replacement. A replacement owns its execution and recording behavior; the builtin agent's interception hooks run only when that agent is used. +To replace the entire model stage, implement either `run_model` or `run_model_stream`. [Your first plugin](/docs/app/plugins/) shows a text replacement. A replacement owns its execution and recording behavior; the builtin agent's interception hooks run only when that agent is used. ## Keep application state across turns @@ -122,7 +122,7 @@ turn_counter = TurnCounter() This in-memory example counts model-stage exits, including failures. It resets when the process exits and assumes one turn at a time per session. Use durable storage and an appropriate concurrency strategy for production state. -`save_state` runs in the model stage's `finally` block, after session, state, and prompt preparation have succeeded. Errors during that preparation skip the save hook. Conversation history has a separate [Tape lifecycle](/docs/concepts/tape-and-context/#state). +`save_state` runs in the model stage's `finally` block, after session, state, and prompt preparation have succeeded. Errors during that preparation skip the save hook. Conversation history has a separate [Tape lifecycle](/docs/concepts/tape/#state). ## Provide a tape store @@ -141,7 +141,7 @@ def provide_tape_store(): return FileTapeStore(Path("./application-tapes")) ``` -This example selects a plain JSONL file store in the current directory. Bub's builtin application store adds application-specific search behavior; choose the store whose behavior you need. For a database-backed application store, see the [SQLAlchemy integration](/docs/tutorials/tapestore-sqlalchemy/). +This example selects a plain JSONL file store in the current directory. Bub's builtin application store adds application-specific search behavior; choose the store whose behavior you need. For a database-backed application store, see the [SQLAlchemy integration](/docs/integrations/storage/). The framework resolves the provider once when `framework.running()` opens. A provider can yield a store and release it in `finally`. The [reference](/docs/reference/hooks/#runtime-resources) covers that lifecycle and the corresponding `provide_lifespan` hook. @@ -160,7 +160,7 @@ def provide_tape_sidecar(): return ArtifactSidecar() ``` -The [shared Tape explanation](/docs/concepts/tape-and-context/) describes sidecar naming and merge behavior. +The [shared Tape explanation](/docs/concepts/tape/) describes sidecar naming and merge behavior. ## Add an application command diff --git a/website/src/content/docs/docs/app/index.mdx b/website/src/content/docs/docs/app/index.mdx new file mode 100644 index 000000000..6754ef02f --- /dev/null +++ b/website/src/content/docs/docs/app/index.mdx @@ -0,0 +1,42 @@ +--- +title: Use the Bub app +description: Run the Bub application, configure it for your work, and choose what to add. +sidebar: + order: 0 +--- + +Start with the running Bub application and add the behavior your work needs. The builtin `BubAgent` extends `core.Agent`; the Framework supplies application orchestration, plugin hooks, and channel routing. + +These guides use the Bub application's configuration and extension points. To supply your own model and tools directly in a Python program, start with [Build your own Bub Agent](/docs/sdk/). + +## Run the app + +Follow the [application installation](/docs/getting-started/install/#app-install) and [run your first conversation](/docs/app/first-session/). Then [configure the model and workspace](/docs/app/configure/) for your work. + +Day to day, choose how Bub reaches you: + +- [Use the terminal](/docs/app/chat/): run one task or continue a conversation. +- [Connect Telegram](/docs/app/telegram/): set up a bot and choose who can use it. +- [Run a gateway](/docs/app/gateway/): keep every enabled channel listening in one process. +- [Deploy the app](/docs/app/deploy/): build the container and keep its data across restarts. + +## Choose what to add + +| Your task | Start here | +| --- | --- | +| Add a callable function for the model | [Tools in the Bub app](/docs/app/tools/) | +| Give the model reusable instructions | [Skills](/docs/app/skills/) | +| Package an integration or replace an application hook | [Plugins](/docs/app/plugins/) and [Hooks](/docs/app/hooks/) | +| Accept messages through CLI, Telegram, or another adapter | [Channels](/docs/app/gateway/) | +| Ship a distribution | [Package a Bub application](/docs/app/package/) | +| Keep a Bub service running | [Deployment](/docs/app/deploy/) | + +Tools, conversation history, and agent events keep their [shared contracts](/docs/concepts/). The app adds its own defaults, including tool loading and code mode selection. [Tools in the Bub app](/docs/app/tools/) explains those policies; [one turn](/docs/concepts/turn/) explains how the Framework connects incoming messages to outgoing responses. + +## Reuse an integration + +- Browse [Bub Hub](https://hub.bub.build) for plugins, skills, and distributions. +- Follow the [integration guides](/docs/integrations/) to connect a service or protocol. +- Check the [application reference](/docs/reference/#application) for signatures and defaults. + +For a service that owns a `core.Agent` directly, follow [Serve an agent with FastAPI](/docs/sdk/web-service/). That route manages models and storage in Python. diff --git a/website/src/content/docs/docs/tutorials/language-extensions.mdx b/website/src/content/docs/docs/app/language-extensions.mdx similarity index 97% rename from website/src/content/docs/docs/tutorials/language-extensions.mdx rename to website/src/content/docs/docs/app/language-extensions.mdx index ea3e71aee..2c7ffe500 100644 --- a/website/src/content/docs/docs/tutorials/language-extensions.mdx +++ b/website/src/content/docs/docs/app/language-extensions.mdx @@ -2,6 +2,7 @@ title: Implement hooks in another language description: Use the experimental Extism plugin to implement selected application hooks in WebAssembly. sidebar: + badge: { text: 'Experimental', variant: 'caution' } order: 5 --- @@ -144,6 +145,6 @@ Check that the plugin supports the hooks you need, and verify the arguments and ## Next steps -- [Building plugins](/docs/build/plugins/): package a Python integration for the Bub application. +- [Building plugins](/docs/app/plugins/): package a Python integration for the Bub application. - [`bub-extism` README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism): read the package conventions and supported hooks. - [Extism overview](https://extism.org/docs/overview): understand how Extism runs wasm modules across languages. diff --git a/website/src/content/docs/docs/build/distribution.mdx b/website/src/content/docs/docs/app/package.mdx similarity index 92% rename from website/src/content/docs/docs/build/distribution.mdx rename to website/src/content/docs/docs/app/package.mdx index ef1e031bc..4a0dd83d4 100644 --- a/website/src/content/docs/docs/build/distribution.mdx +++ b/website/src/content/docs/docs/app/package.mdx @@ -7,7 +7,7 @@ sidebar: Build on the Bub app when you want its configuration, plugins, and message routing in your product. You can host `BubFramework` in an existing process or ship a command that starts it. -This path uses the application dependencies in [Install Bub](/docs/getting-started/install/). For a program that directly composes an Agent and its components, follow [Build your own agent](/docs/build/sdk/). +This path uses the application dependencies in [Install Bub](/docs/getting-started/install/). For a program that directly composes an Agent and its components, follow [Build your own agent](/docs/sdk/). @@ -49,7 +49,7 @@ Keep `framework.running()` open for the service's lifetime. It acquires hook-pro ## Reuse plugins in your own service -A host can use plugin-provided tools, stores, or hooks while keeping its own API and stream handling. Follow [Reuse plugins in your agent](/docs/build/reuse-plugins/) to connect those capabilities to `core.Agent`. +A host can use plugin-provided tools, stores, or hooks while keeping its own API and stream handling. Follow [Reuse plugins in your agent](/docs/sdk/app-plugins/) to connect those capabilities to `core.Agent`. `BubAgent` also carries the application's prompts and channel delivery conventions. Choose it when you want those application behaviors, and provide the corresponding output handling in your host. @@ -87,6 +87,6 @@ channels = "my_bub.channels:ChannelPlugin" packages = ["src/my_bub", "src/skills"] ``` -Each target follows the [plugin registration rules](/docs/build/plugins/). Include task instructions using the [skills package layout](/docs/build/skills/#ship-skills-with-your-package). +Each target follows the [plugin registration rules](/docs/app/plugins/). Include task instructions using the [skills package layout](/docs/app/skills/#ship-skills-with-your-package). Before release, install the built wheel into a separate environment. Run the command, confirm the plugin report, load a packaged skill, and exercise a representative conversation. For a hosted application, also check shutdown while a stream or background tool is active. diff --git a/website/src/content/docs/docs/app/plugins.mdx b/website/src/content/docs/docs/app/plugins.mdx new file mode 100644 index 000000000..c6521683f --- /dev/null +++ b/website/src/content/docs/docs/app/plugins.mdx @@ -0,0 +1,162 @@ +--- +title: Plugins +description: Write a plugin, choose its entry point, and install it into the app. +sidebar: + order: 0 +--- + +A plugin changes how the Bub app handles a task. This page builds a small echo plugin so you can check discovery and turn processing without model credentials, then covers the entry-point choices, tool registration, and sharing. + +A Python package declares an entry point in the `bub` group; Bub imports that target and registers its hook implementations. You can also reuse an existing plugin in your own service while keeping your host's entry point; see [Borrow the app runtime](/docs/sdk/app-plugins/). + +## Create the package + +Install Python 3.12+ and uv as described in [Install Bub](/docs/getting-started/install/). This tutorial creates a project environment containing Bub 0.6.0 and your plugin. + +Create this package layout: + +```text +bub-echo-plugin/ +├── pyproject.toml +└── src/ + └── bub_echo_plugin/ + ├── __init__.py + └── plugin.py +``` + +Leave `__init__.py` empty. Put this in `pyproject.toml`: + +```toml +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "bub-echo-plugin" +version = "0.1.0" +requires-python = ">=3.12" +dependencies = ["bub[app]>=0.6.0"] + +[project.entry-points."bub"] +echo = "bub_echo_plugin.plugin:echo_plugin" + +[tool.hatch.build.targets.wheel] +packages = ["src/bub_echo_plugin"] +``` + +The `bub` entry-point group tells the application where to find the plugin. + +## Implement the response + +Put this in `src/bub_echo_plugin/plugin.py`: + +```python +from bub import hookimpl + + +class EchoPlugin: + @hookimpl + def run_model(self, prompt, session_id, state): + return f"[echo:{session_id}] {prompt}" + + +echo_plugin = EchoPlugin() +``` + +The application still builds the incoming prompt. Your `run_model` implementation supplies the response instead of calling the builtin agent. + +## Install and try it + +From the plugin directory, install the project and its dependencies: + +```bash +uv sync +uv run bub hooks +``` + +`uv sync` installs this package in editable mode. Run Bub through `uv run` in the same project so it can discover the plugin. To install into an existing activated application environment instead, use `uv pip install -e .` and run `bub hooks` there. + +The report should include `echo` under `run_model`. The builtin agent appears under `run_model_stream`. + +```bash +uv run bub run "hello from my plugin" +``` + +With no other plugin overriding the model stage, the response contains: + +```text +[echo:cli:local] hello from my plugin +``` + +If `echo` is missing, check that the package was installed in the environment running `bub`, its entry-point group, and startup logs for an import failure. Changes to package metadata require reinstalling the package. If it is listed but another response wins, inspect the other model hooks and their priority. + +Bub registers builtin hooks before entry-point plugins. Hooks at equal priority generally run in reverse registration order; `tryfirst` and `trylast` affect that order. The [dispatch rules](/docs/reference/hooks/#iteration-order) explain which implementation runs first. + +## Choose an entry-point target + +The key `myplugin` identifies the plugin in diagnostics. The value names the Python object to import. Choose one target form: + +| Target | Registration | Use it when | +| --- | --- | --- | +| `bub_myplugin.plugin` | Register the module | Hooks are module-level functions | +| `bub_myplugin.plugin:plugin` | Register the instance | Hooks share state on an object | +| `bub_myplugin.plugin:MyPlugin` | Call the class or factory with `BubFramework`, then register its result | Hooks need the framework | + +For the module form, define hooks at module level: + +```python +from bub import hookimpl + + +@hookimpl +def build_prompt(message, session_id, state): + return "Describe this workspace." +``` + +For the instance form, create the object in the target module: + +```python +from bub import hookimpl + + +class MyPlugin: + @hookimpl + def build_prompt(self, message, session_id, state): + return "Describe this workspace." + + +plugin = MyPlugin() +``` + +Every callable target receives the framework, including classes. Accept it in the constructor or factory: + +```python +from bub import BubFramework, hookimpl + + +class MyPlugin: + def __init__(self, framework: BubFramework) -> None: + self.framework = framework + + @hookimpl + def build_prompt(self, message, session_id, state): + return f"Describe the workspace at {self.framework.workspace}." +``` + +These examples replace the prompt. Choose the hook for your actual task from [Change application behavior](/docs/app/hooks/). + +## Register tools during import + +If the package defines `@tool` functions, import their module from the entry-point module before an agent is created: + +```python +from . import tools # noqa: F401 +``` + +The application takes a snapshot of registered tools when it creates a `BubAgent`. See [Add application tools](/docs/app/tools/) for registration, deferred loading, and code mode. + +## Share the plugin + +Build and install a wheel into a separate environment before publishing it. Confirm that the entry point loads and that a representative task uses your hook. If you bundle skills or several plugins, follow [Package a Bub application](/docs/app/package/). + +Portable Agent Plugins use a different format: `plugin.json`, skills, and MCP servers. The [Agent Plugins integration](/docs/integrations/agent-plugins/) loads those packages through a Python plugin. diff --git a/website/src/content/docs/docs/build/skills.mdx b/website/src/content/docs/docs/app/skills.mdx similarity index 60% rename from website/src/content/docs/docs/build/skills.mdx rename to website/src/content/docs/docs/app/skills.mdx index 26cb6118b..b6d1ed3fd 100644 --- a/website/src/content/docs/docs/build/skills.mdx +++ b/website/src/content/docs/docs/app/skills.mdx @@ -1,15 +1,56 @@ --- -title: Package skills for the app -description: Share task instructions with support files and make them discoverable by Bub. +title: Skills +description: Write a reusable procedure, check that the app loads it, and package it for other users. sidebar: - order: 4 + order: 0 --- -A skill gives the Bub app a procedure it can load for a task. Keep project-specific skills in the workspace, or include them in a Python package so other users can install them. +A skill gives the Bub app a procedure it can load for a task. This page walks through a project skill, then covers the file fields, discovery order, and packaging. Keep project-specific skills in the workspace, or include them in a Python package so other users can install them. -For your first local skill, follow [Add a project skill](/docs/getting-started/first-skill/). This page covers discovery and packaging. Skill discovery is part of the application; a plain `core.Agent` uses the instructions you explicitly give it. +Skill discovery is part of the application; a plain `core.Agent` uses the instructions you explicitly give it. -## Write a skill Bub can load +## Write the instructions + +Create a directory for the skill: + +```bash +mkdir -p .agents/skills/repo-map +``` + +Create `.agents/skills/repo-map/SKILL.md`: + +```markdown +--- +name: repo-map +description: Read the local repository layout before changing code. +--- + +1. Run `rg --files` to see the repository layout. +2. Read the files that explain the requested change. +3. Describe the affected modules before editing code. +``` + +The `name` must match the directory name. Use lowercase words separated by hyphens. The description helps the model decide when to use the skill. + +## Check that Bub can load it + +```bash +bub run ",skill name=repo-map" +``` + +The command prints the skill's instructions without calling a model. If it returns `(no such skill)`, check the path and the YAML frontmatter. + +## Use it in a conversation + +With your model configured, run: + +```bash +bub run 'Use the $repo-map skill, then list the top-level modules you found.' +``` + +`$repo-map` includes the skill's instructions in this turn's system prompt. The model then follows them using the tools available in the application. Install `rg` in the execution environment for this example. + +## Skill file fields Each skill directory contains a `SKILL.md` with YAML frontmatter: @@ -84,4 +125,4 @@ bub run ",skill name=my-skill" If it returns `(no such skill)`, check the directory name, frontmatter, and wheel contents. If it prints an unexpected version, look for a project or user skill with the same name. -To distribute a CLI and plugins alongside these skills, see [Package a Bub application](/docs/build/distribution/). +To distribute a CLI and plugins alongside these skills, see [Package a Bub application](/docs/app/package/). diff --git a/website/src/content/docs/docs/operate/channels/telegram.mdx b/website/src/content/docs/docs/app/telegram.mdx similarity index 94% rename from website/src/content/docs/docs/operate/channels/telegram.mdx rename to website/src/content/docs/docs/app/telegram.mdx index f106c4136..1453f5323 100644 --- a/website/src/content/docs/docs/operate/channels/telegram.mdx +++ b/website/src/content/docs/docs/app/telegram.mdx @@ -11,7 +11,7 @@ The Telegram channel is registered under the name `telegram`. It uses long polli - A bot exists in [@BotFather](https://t.me/BotFather) and you have its token. - For group chats, **Privacy Mode is disabled** in BotFather. Otherwise the bot only sees commands, and the inbound filter cannot decide whether to respond. -- Bub is configured with a working model: see [Configure](/docs/operate/configure/). +- Bub is configured with a working model: see [Configure](/docs/app/configure/). ## 1. Set the token @@ -75,7 +75,7 @@ Inbound handling: ## 6. Debounce -The Telegram channel sets `needs_debounce=True`. Inbound messages are batched per session using `BUB_DEBOUNCE_SECONDS`, `BUB_MAX_WAIT_SECONDS`, and `BUB_ACTIVE_TIME_WINDOW`. See [Channels overview](/docs/operate/channels/) for the values. +The Telegram channel sets `needs_debounce=True`. Inbound messages are batched per session using `BUB_DEBOUNCE_SECONDS`, `BUB_MAX_WAIT_SECONDS`, and `BUB_ACTIVE_TIME_WINDOW`. See [Channels overview](/docs/app/gateway/) for the values. :::tip Comma commands (`,help`, `,fs.read ...`) always bypass debounce. They are dispatched the moment they arrive, regardless of any pending batch. @@ -89,6 +89,6 @@ Comma commands are different: they keep `output_channel="telegram"`, so their to ## Next steps -- [Channels overview](/docs/operate/channels/): debounce, `enabled_channels`, session ids. -- [Deploy](/docs/operate/deploy/): package the bot into a container with persistent state. +- [Channels overview](/docs/app/gateway/): debounce, `enabled_channels`, session ids. +- [Deploy](/docs/app/deploy/): package the bot into a container with persistent state. - [Settings reference](/docs/reference/settings/): every `BUB_TELEGRAM_*` variable. diff --git a/website/src/content/docs/docs/build/app-tools.mdx b/website/src/content/docs/docs/app/tools.mdx similarity index 82% rename from website/src/content/docs/docs/build/app-tools.mdx rename to website/src/content/docs/docs/app/tools.mdx index 5edf1f0ef..0ccab5f08 100644 --- a/website/src/content/docs/docs/build/app-tools.mdx +++ b/website/src/content/docs/docs/app/tools.mdx @@ -5,7 +5,7 @@ sidebar: order: 3 --- -Give the Bub app an action it can perform for your work. The [shared tools guide](/docs/build/tools/) explains function schemas, results, and tool context. This page covers application registration and the policies applied by `BubAgent`. +Give the Bub app an action it can perform for your work. The [shared tools guide](/docs/sdk/tools/) explains function schemas, results, and tool context. This page covers application registration and the policies applied by `BubAgent`. ## Register a tool @@ -21,7 +21,7 @@ def get_order(order_id: str) -> dict[str, str]: return {"id": order_id, "status": "shipped"} ``` -The decorator only creates a `Tool`. Provide it from your [plugin](/docs/build/plugins/) with the `provide_tools` hook: +The decorator only creates a `Tool`. Provide it from your [plugin](/docs/app/plugins/) with the `provide_tools` hook: ```python from bub import hookimpl @@ -44,7 +44,7 @@ Run `bub hooks` to check that your plugin implements `provide_tools`. The model- When calling `BubAgent.run_stream`, pass `allowed_tools` to limit the model's tools for that turn. It accepts runtime names or model-facing names. `None` allows all instance tools; an empty collection allows none. Unknown names raise `ValueError`. -This filter applies to model and code-mode tools. Comma commands use the agent's instance tools directly. Use [tool-call decisions](/docs/build/hooks/#control-calls) when a rule must also govern command execution. +This filter applies to model and code-mode tools. Comma commands use the agent's instance tools directly. Use [tool-call decisions](/docs/app/hooks/#control-calls) when a rule must also govern command execution. @@ -89,9 +89,9 @@ When `run_code` is allowed, the app selects tools according to `exposure`: If `run_code` is excluded, the turn uses the code-mode-off selection. The app writes a Python tool stub under `/codemode/` and includes its path in the system prompt. Python calls such as `await tools.orders_get(order_id="123")` use the selected tools and pass through the application's interception hooks. -The app connects CodeMode to the session's environment and model settings. Code can use the Republic bridge and media attachment tools described in [CodeMode](/docs/build/tools/#code-mode). Model credentials remain on the host when using the bridge; the default local execution environment still runs code on that host. +The app connects CodeMode to the session's environment and model settings. Code can use the Republic bridge and media attachment tools described in [CodeMode](/docs/sdk/tools/#code-mode). Model credentials remain on the host when using the bridge; the default local execution environment still runs code on that host. -`BubAgent` interprets `state["code_mode"]` and the `deferred` flag to apply these selection rules. With a plain `core.Agent`, [compose CodeMode explicitly](/docs/build/tools/#code-mode) and supply your own deferred-loading policy if you need one. +`BubAgent` interprets `state["code_mode"]` and the `deferred` flag to apply these selection rules. With a plain `core.Agent`, [compose CodeMode explicitly](/docs/sdk/tools/#code-mode) and supply your own deferred-loading policy if you need one. @@ -99,12 +99,12 @@ The app connects CodeMode to the session's environment and model settings. Code Builtin `bash`, `bash.output`, `bash.kill`, `fs.*`, and `run_code` use the session's `Environment`. The default is `LocalEnvironment`, which runs on the host. -Provide another environment through a plugin's `provide_environment(session_id, workspace)` hook. The framework caches one environment per session and closes it when `framework.running()` exits. The [shared tools guide](/docs/build/tools/#run-tools-in-an-environment) describes the environment interface. +Provide another environment through a plugin's `provide_environment(session_id, workspace)` hook. The framework caches one environment per session and closes it when `framework.running()` exits. The [shared tools guide](/docs/sdk/tools/#run-tools-in-an-environment) describes the environment interface. A custom application tool can access that environment with `environment_from_state(context.state)` from `bub.builtin.environment`. The Python tool runs on the host and explicitly delegates operations to the environment when needed. ## Add instructions alongside the tool -Put workspace-wide instructions in `AGENTS.md`. Use [skills](/docs/build/skills/) for procedures the model can load for a task. A skill can tell the model when to call your tool; the Python function defines what that tool does. +Put workspace-wide instructions in `AGENTS.md`. Use [skills](/docs/app/skills/) for procedures the model can load for a task. A skill can tell the model when to call your tool; the Python function defines what that tool does. -Comma commands let a person invoke registered tools without asking the model. See [text commands](/docs/build/tools/#commands) for command syntax. +Comma commands let a person invoke registered tools without asking the model. See [text commands](/docs/sdk/tools/#commands) for command syntax. diff --git a/website/src/content/docs/docs/build/index.mdx b/website/src/content/docs/docs/build/index.mdx deleted file mode 100644 index 127ba44b9..000000000 --- a/website/src/content/docs/docs/build/index.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Customize the Bub app -description: Extend the Bub application with tools, skills, plugins, and channels, or embed its application pipeline. -sidebar: - order: 0 ---- - -Start with the running Bub application and add the behavior your work needs. The builtin `BubAgent` extends `core.Agent`; the Framework supplies application orchestration, plugin hooks, and channel routing. - -The guides below use the Bub application's configuration and extension points. To supply your own model and tools directly in a Python program, start with [Build your own agent](/docs/build/sdk/). - -## Run the app - -Follow the [application installation](/docs/getting-started/install/#app-install) and [run your first conversation](/docs/getting-started/first-turn/). Then [configure the model and workspace](/docs/operate/configure/) for your work. - -## Choose what to add - -| Your task | Start here | -| --- | --- | -| Add a callable function for the model | [Tools in the Bub app](/docs/build/app-tools/) | -| Give the model reusable instructions | [Write your first skill](/docs/getting-started/first-skill/), then [Skills](/docs/build/skills/) | -| Package an integration or replace an application hook | [Write your first plugin](/docs/getting-started/first-plugin/), then [Plugins](/docs/build/plugins/) and [Hooks](/docs/build/hooks/) | -| Accept messages through CLI, Telegram, or another adapter | [Channels](/docs/operate/channels/) | -| Embed the application's message pipeline or ship a distribution | [Embedding and distributions](/docs/build/distribution/) | -| Keep a Bub service running | [Deployment](/docs/operate/deploy/) | - -Tools, conversation history, and agent events keep their [shared contracts](/docs/concepts/). The app adds its own defaults, including tool loading and code mode selection. [Tools in the Bub app](/docs/build/app-tools/) explains those policies; [the application turn pipeline](/docs/concepts/turn-pipeline/) explains how the Framework connects incoming messages to outgoing responses. - -## Reuse an integration - -- Browse [Bub Hub](https://hub.bub.build) for plugins, skills, and distributions. -- Follow the [integration guides](/docs/tutorials/) to connect a service or protocol. -- Check the [application reference](/docs/reference/#application) for signatures and defaults. diff --git a/website/src/content/docs/docs/build/plugins.mdx b/website/src/content/docs/docs/build/plugins.mdx deleted file mode 100644 index 659a3a164..000000000 --- a/website/src/content/docs/docs/build/plugins.mdx +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: Package a plugin -description: Make your application's hooks discoverable through a Python package. -sidebar: - order: 1 ---- - -Use a plugin to give the Bub app behavior you want to reuse or share. A Python package declares an entry point in the `bub` group; Bub imports that target and registers its hook implementations. You can also [reuse an existing plugin in your own service](/docs/build/reuse-plugins/) while keeping your host's entry point. - -For a complete first example, follow [Write your first plugin](/docs/getting-started/first-plugin/). This guide covers the choices you make when packaging a plugin of your own. - -Portable Agent Plugins use a different format: `plugin.json`, skills, and MCP servers. The [Agent Plugins integration](/docs/tutorials/agent-plugins/) loads those packages through a Python plugin. - -## Declare an entry point - -A plugin needs the application dependencies described in [Install Bub](/docs/getting-started/install/). Declare `bub[app]>=0.6.0` in the package dependencies for this guide. See [Upgrade to 0.6.0](/docs/reference/versions/) when updating an existing plugin. - -```toml -[project.entry-points."bub"] -myplugin = "bub_myplugin.plugin" -``` - -The key `myplugin` identifies the plugin in diagnostics. The value names the Python object to import. - -Choose one target form: - -| Target | Registration | Use it when | -| --- | --- | --- | -| `bub_myplugin.plugin` | Register the module | Hooks are module-level functions | -| `bub_myplugin.plugin:plugin` | Register the instance | Hooks share state on an object | -| `bub_myplugin.plugin:MyPlugin` | Call the class or factory with `BubFramework`, then register its result | Hooks need the framework | - -For the module form, define hooks at module level: - -```python -from bub import hookimpl - - -@hookimpl -def build_prompt(message, session_id, state): - return "Describe this workspace." -``` - -For the instance form, create the object in the target module: - -```python -from bub import hookimpl - - -class MyPlugin: - @hookimpl - def build_prompt(self, message, session_id, state): - return "Describe this workspace." - - -plugin = MyPlugin() -``` - -Every callable target receives the framework, including classes. Accept it in the constructor or factory: - -```python -from bub import BubFramework, hookimpl - - -class MyPlugin: - def __init__(self, framework: BubFramework) -> None: - self.framework = framework - - @hookimpl - def build_prompt(self, message, session_id, state): - return f"Describe the workspace at {self.framework.workspace}." -``` - -These examples replace the prompt. Choose the hook for your actual task from [Change application behavior](/docs/build/hooks/). - -## Register tools during import - -If the package defines `@tool` functions, import their module from the entry-point module before an agent is created: - -```python -from . import tools # noqa: F401 -``` - -The application takes a snapshot of registered tools when it creates a `BubAgent`. See [Add application tools](/docs/build/app-tools/) for registration, deferred loading, and code mode. - -## Install into the application's environment - -An editable install lets you change Python source without rebuilding the package. Use the same environment that runs Bub. - -For a uv project, run from the plugin directory: - -```bash -uv sync -uv run bub hooks -``` - -To install into an existing activated application environment, use `uv pip install -e .` and run `bub hooks` there. - -`bub hooks` confirms registration. Check priority and return values by running the task your hook should handle. Bub registers builtin hooks before entry-point plugins. Hooks at equal priority generally run in reverse registration order; `tryfirst` and `trylast` affect that order. The [dispatch rules](/docs/reference/hooks/#iteration-order) explain which implementation runs first. - -If the plugin is missing, check its entry-point group, the active environment, and startup logs for an import failure. Changes to package metadata require reinstalling the package. - -## Share the plugin - -Build and install a wheel into a separate environment before publishing it. Confirm that the entry point loads and that a representative task uses your hook. If you bundle skills or several plugins, follow [Package a Bub application](/docs/build/distribution/). diff --git a/website/src/content/docs/docs/concepts/philosophy.mdx b/website/src/content/docs/docs/concepts/design.mdx similarity index 90% rename from website/src/content/docs/docs/concepts/philosophy.mdx rename to website/src/content/docs/docs/concepts/design.mdx index 643505a54..45e3650ad 100644 --- a/website/src/content/docs/docs/concepts/philosophy.mdx +++ b/website/src/content/docs/docs/concepts/design.mdx @@ -13,19 +13,19 @@ Bub lets you choose how much of the application to use. You can connect the agen `BubAgent` adds the application's defaults: settings, skills, commands, tool selection, and steering. `BubFramework` manages plugins, message processing, and outbound routing around it. Choose them when you want to reuse that behavior. -The [installation guide](/docs/getting-started/install/) explains the dependency choices. The [SDK guide](/docs/build/sdk/) and [application guide](/docs/build/) show how to put them to work. +The [installation guide](/docs/getting-started/install/) explains the dependency choices. The [SDK guide](/docs/sdk/) and [application guide](/docs/app/) show how to put them to work. ## Keep history and context separate A conversation can be longer than the context needed for the next step. Tape keeps the recorded history; context selection decides what the model reads. An anchor starts a new context without deleting earlier work. -That makes it possible to inspect saved messages and tool results while keeping the next request focused. Persistence still depends on the store you choose, and custom application state needs an explicit storage policy. See [Conversation history and context](/docs/concepts/tape-and-context/). +That makes it possible to inspect saved messages and tool results while keeping the next request focused. Persistence still depends on the store you choose, and custom application state needs an explicit storage policy. See [Conversation history and context](/docs/concepts/tape/). ## Make extension behavior visible A tool describes an action. A skill describes a procedure. A channel connects the application to a place where people work. Keeping these roles distinct helps you choose what to change. -At runtime, events expose model and tool calls. Application hooks also cover configuration, resource setup, and message routing. Each has an ordering and error contract; the [extension guide](/docs/concepts/surfaces/) points to the appropriate one. +At runtime, events expose model and tool calls. Application hooks also cover configuration, resource setup, and message routing. Each has an ordering and error contract; the [extension guide](/docs/concepts/extension-points/) points to the appropriate one. ## Background diff --git a/website/src/content/docs/docs/concepts/events.mdx b/website/src/content/docs/docs/concepts/events.mdx index d8e485fbb..9f527fa8a 100644 --- a/website/src/content/docs/docs/concepts/events.mdx +++ b/website/src/content/docs/docs/concepts/events.mdx @@ -13,7 +13,7 @@ Three kinds of information serve different purposes: | --- | --- | --- | | `AgentEvent` | Internal lifecycle and call interception. | `agent.events.on(...)` | | `StreamEvent` | Output for the caller, such as text and tool progress. | Iterate the stream returned by `agent.run()`. | -| `TapeEntry` | Saved conversation and selected runtime records. | Query the [Tape](/docs/concepts/tape-and-context/). | +| `TapeEntry` | Saved conversation and selected runtime records. | Query the [Tape](/docs/concepts/tape/). | The agent's loop controls execution and invokes event handlers inline at defined points. TapeRecorder saves selected exchanges and lifecycle records; your handlers can record other events you need to keep. @@ -21,7 +21,7 @@ The agent's loop controls execution and invokes event handlers inline at defined ## Register a handler -With the `agent` from the [SDK guide](/docs/build/sdk/): +With the `agent` from the [SDK guide](/docs/sdk/): ```python from bub.core.events import StepFinished, ToolCallFinished @@ -79,7 +79,7 @@ To change arguments and continue, replace `event.call` with `dataclasses.replace | `ChatFinished` | A step's exchange is ready to record. | Observe the exchange. | | `CommandFinished` | A direct command finishes or fails. | Observe command, status, and output. | -Command prompts execute tools directly, emitting tool events and `CommandFinished`. See [Commands](/docs/build/tools/#commands). +Command prompts execute tools directly, emitting tool events and `CommandFinished`. See [Commands](/docs/sdk/tools/#commands). All agent events carry turn `state`. Events derived from `TapeEvent` also carry the active tape. For tool events, the tool context is available as `event.call.context` when the invocation has one. @@ -128,10 +128,10 @@ async def mark_reviewed() -> None: ## Use events from an application plugin -`AgentPlugins` connects the `before_llm_call`, `after_llm_call`, `before_tool_call`, and `after_tool_call` hooks to the call events, the `continue_prompt` hook to `StepFinished`, and the steering inbox to `MessagesPending`. Plugins return the values required by the hook contract; the adapter updates the corresponding event. BubAgent uses this adapter, and a core Agent can [use it too](/docs/build/reuse-plugins/#agent-plugins). +`AgentPlugins` connects the `before_llm_call`, `after_llm_call`, `before_tool_call`, and `after_tool_call` hooks to the call events, the `continue_prompt` hook to `StepFinished`, and the steering inbox to `MessagesPending`. Plugins return the values required by the hook contract; the adapter updates the corresponding event. BubAgent uses this adapter, and a core Agent can [use it too](/docs/sdk/app-plugins/#agent-plugins). This adapter follows plugin hook ordering and catches exceptions from individual interception hooks, logging and skipping them. To deny a call, return the appropriate decision. Other application hooks follow their own error contracts. -Use [application hooks](/docs/build/hooks/) for plugin examples and the [hooks reference](/docs/reference/hooks/) for ordering and signatures. EventBus is included in the base SDK installation. +Use [application hooks](/docs/app/hooks/) for plugin examples and the [hooks reference](/docs/reference/hooks/) for ordering and signatures. EventBus is included in the base SDK installation. `framework.events` publishes commands, channel work, and user prompts through `CommandChanged`, `WorkChanged`, and `PromptChanged` from `bub.application`. Subscribe and unsubscribe with the same `on()` and `off()` API. diff --git a/website/src/content/docs/docs/concepts/surfaces.mdx b/website/src/content/docs/docs/concepts/extension-points.mdx similarity index 58% rename from website/src/content/docs/docs/concepts/surfaces.mdx rename to website/src/content/docs/docs/concepts/extension-points.mdx index 9692359fe..542113891 100644 --- a/website/src/content/docs/docs/concepts/surfaces.mdx +++ b/website/src/content/docs/docs/concepts/extension-points.mdx @@ -9,25 +9,25 @@ Start with the behavior you want to add. A new action needs a tool; a new place | You want to | Use | Where it belongs | | --- | --- | --- | -| Let the agent query or change something | A [tool](/docs/build/tools/). | Shared core capability. Pass it to Agent, or register it with the app. | +| Let the agent query or change something | A [tool](/docs/sdk/tools/). | Shared core capability. Pass it to Agent, or register it with the app. | | Observe or intercept a model or tool call | An [event handler](/docs/concepts/events/). | Core EventBus; app plugins can use the corresponding interception hooks. | -| Change which history becomes model messages | A [TapeContext](/docs/concepts/tape-and-context/#context). | Pass it to Agent, or provide it through an app hook. | -| Teach a reusable procedure | A [skill](/docs/build/skills/). | Discovered and loaded by the Bub application. | -| Change application setup or message processing | A [plugin](/docs/build/plugins/). | BubFramework and its hooks. | -| Connect a messaging service | A [channel](/docs/operate/channels/). | The application's inbound and outbound routing. | +| Change which history becomes model messages | A [TapeContext](/docs/concepts/tape/#context). | Pass it to Agent, or provide it through an app hook. | +| Teach a reusable procedure | A [skill](/docs/app/skills/). | Discovered and loaded by the Bub application. | +| Change application setup or message processing | A [plugin](/docs/app/plugins/). | BubFramework and its hooks. | +| Connect a messaging service | A [channel](/docs/app/gateway/). | The application's inbound and outbound routing. | ## Actions and instructions A tool executes Python and returns a result. A skill is a directory containing `SKILL.md` and optional supporting files. Its instructions can tell the agent how to use tools. You supply the Python implementations those instructions refer to separately. -Plain Agent uses the tools and prompt sections you supply. The Bub application discovers skills and reads `AGENTS.md`; see [Skills](/docs/build/skills/) and [Application tools](/docs/build/app-tools/). +Plain Agent uses the tools and prompt sections you supply. The Bub application discovers skills and reads `AGENTS.md`; see [Skills](/docs/app/skills/) and [Application tools](/docs/app/tools/). -A tool can also be called by a [text command](/docs/build/tools/#commands) or by generated Python through CodeMode. These invocation paths share the same tool implementation. +A tool can also be called by a [text command](/docs/sdk/tools/#commands) or by generated Python through CodeMode. These invocation paths share the same tool implementation. ## Messages and application state Channels deliver an inbound envelope and send outbound envelopes. The application uses these envelopes for content, routing, and session identity. Its hooks share a mutable `TurnState` dictionary for the current turn. -The [application turn pipeline](/docs/concepts/turn-pipeline/) prepares that state and prompt before it calls the agent. Direct SDK callers supply their prompt and state to Agent themselves. +The [application turn pipeline](/docs/concepts/turn/) prepares that state and prompt before it calls the agent. Direct SDK callers supply their prompt and state to Agent themselves. -Choose dependencies for the capabilities you need, and choose the calling interface for your host. A Python service can reuse application plugins while retaining its own request handling and streams. [Reuse plugins in your agent](/docs/build/reuse-plugins/) shows direct component reuse and how framework hooks fit into an embedded host. +Choose dependencies for the capabilities you need, and choose the calling interface for your host. A Python service can reuse application plugins while retaining its own request handling and streams. [Reuse plugins in your agent](/docs/sdk/app-plugins/) shows direct component reuse and how framework hooks fit into an embedded host. diff --git a/website/src/content/docs/docs/concepts/index.mdx b/website/src/content/docs/docs/concepts/index.mdx index 9ff09012e..6ca894df6 100644 --- a/website/src/content/docs/docs/concepts/index.mdx +++ b/website/src/content/docs/docs/concepts/index.mdx @@ -5,14 +5,14 @@ sidebar: order: 0 --- -Tools, conversation history, and events work through the same core interfaces in both routes. Use these pages when you need to understand a component or choose how to connect it. You can start with [your own Agent](/docs/build/sdk/) or [the Bub application](/docs/build/) and return here as needed. +Tools, conversation history, and events work through the same core interfaces in both routes. Use these pages when you need to understand a component or choose how to connect it. You can start with [your own Agent](/docs/sdk/) or [the Bub application](/docs/app/) and return here as needed. | You want to understand | Read | | --- | --- | -| How the core and application fit together | [Design choices](/docs/concepts/philosophy/) | -| Which extension point fits a task | [Choose an extension point](/docs/concepts/surfaces/) | -| How to define an action or use generated Python | [Tools and CodeMode](/docs/build/tools/) | -| What gets saved and what the model reads | [Conversation history and context](/docs/concepts/tape-and-context/) | +| How the core and application fit together | [Design choices](/docs/concepts/design/) | +| Which extension point fits a task | [Choose an extension point](/docs/concepts/extension-points/) | +| How to define an action or use generated Python | [Tools and CodeMode](/docs/sdk/tools/) | +| What gets saved and what the model reads | [Conversation history and context](/docs/concepts/tape/) | | How to observe, change, or stop a call | [Events and call control](/docs/concepts/events/) | ## A few terms @@ -21,4 +21,4 @@ An **Agent** runs the model and tool loop. A **run** handles one prompt and may A **Tape** holds conversation records. Its **entries** include messages, tool calls, results, and events. An **anchor** marks a starting point for building context; a **handoff** writes a new anchor. The run's mutable **state** is a separate dictionary shared with tools and handlers. -The Bub application adds **plugins**, which provide Python hooks, **skills**, which provide reusable instructions, and **channels**, which carry messages in and out. Its [turn pipeline](/docs/concepts/turn-pipeline/) connects those components around the agent. +The Bub application adds **plugins**, which provide Python hooks, **skills**, which provide reusable instructions, and **channels**, which carry messages in and out. Its [turn pipeline](/docs/concepts/turn/) connects those components around the agent. diff --git a/website/src/content/docs/docs/concepts/tape-and-context.mdx b/website/src/content/docs/docs/concepts/tape.mdx similarity index 94% rename from website/src/content/docs/docs/concepts/tape-and-context.mdx rename to website/src/content/docs/docs/concepts/tape.mdx index f688843bf..7cefa0933 100644 --- a/website/src/content/docs/docs/concepts/tape-and-context.mdx +++ b/website/src/content/docs/docs/concepts/tape.mdx @@ -97,13 +97,13 @@ Pass `merge_back=False` to discard the fork's records on exit. This affects tape For a block of work you control directly, use `agent.session(name, merge_back=...)` or `tape.fork_tape(merge_back=...)`. Both merge on exit by default, including exceptional exits. -Serialize runs that share a tape when they must see each other's completed work. Your application provides the session lock. The [FastAPI example](/docs/tutorials/fastapi/) shows locking within one process; multiple workers need coordination across processes. +Serialize runs that share a tape when they must see each other's completed work. Your application provides the session lock. The [FastAPI example](/docs/sdk/web-service/) shows locking within one process; multiple workers need coordination across processes. ## Keep large results alongside the conversation A `TapeSidecar` stores related data in a separate tape. Mounted sidecars follow the main tape through forks, merges, and resets, while their entries stay out of the main context. Pass sidecars explicitly to `Agent(sidecars=...)` when your application needs them. -The Bub application mounts a spill sidecar for large tool results. It keeps a bounded preview and a handle in the conversation, then lets the agent read portions with `spill.read`. During a fork merge or tape reset, sidecar failures are recorded separately while the main tape operation can still complete. The app's spill limits belong to its [configuration](/docs/operate/configure/). +The Bub application mounts a spill sidecar for large tool results. It keeps a bounded preview and a handle in the conversation, then lets the agent read portions with `spill.read`. During a fork merge or tape reset, sidecar failures are recorded separately while the main tape operation can still complete. The app's spill limits belong to its [configuration](/docs/app/configure/). @@ -111,6 +111,6 @@ The Bub application mounts a spill sidecar for large tool results. It keeps a bo BubAgent uses the store provided by BubFramework. The built-in application uses `FuzzyFileTapeStore` under Bub's home directory, normally `~/.bub/tapes/`. Its tape name combines hashes of the workspace path and session ID so the same session ID in different workspaces has separate history. -Applications replace that store through `provide_tape_store` and select context through `build_tape_context`. The [SQLAlchemy guide](/docs/tutorials/tapestore-sqlalchemy/) demonstrates this plugin route. For a direct SDK service, pass your implementation to `Agent(store=...)` as described above. +Applications replace that store through `provide_tape_store` and select context through `build_tape_context`. The [SQLAlchemy guide](/docs/integrations/storage/) demonstrates this plugin route. For a direct SDK service, pass your implementation to `Agent(store=...)` as described above. The built-in app restores selected session settings from tape records. Other turn-state values need their own persistence logic. diff --git a/website/src/content/docs/docs/concepts/turn-pipeline.mdx b/website/src/content/docs/docs/concepts/turn.mdx similarity index 93% rename from website/src/content/docs/docs/concepts/turn-pipeline.mdx rename to website/src/content/docs/docs/concepts/turn.mdx index 3e515837d..8c9363a50 100644 --- a/website/src/content/docs/docs/concepts/turn-pipeline.mdx +++ b/website/src/content/docs/docs/concepts/turn.mdx @@ -45,4 +45,4 @@ If the model stage raises an unhandled exception, the framework runs `save_state ## Extend the app -Use [plugin setup](/docs/build/plugins/) to register your implementation, [hook recipes](/docs/build/hooks/) for specific changes, and the [hooks reference](/docs/reference/hooks/) for exact signatures and ordering. +Use [plugin setup](/docs/app/plugins/) to register your implementation, [hook recipes](/docs/app/hooks/) for specific changes, and the [hooks reference](/docs/reference/hooks/) for exact signatures and ordering. diff --git a/website/src/content/docs/docs/getting-started/first-plugin.mdx b/website/src/content/docs/docs/getting-started/first-plugin.mdx deleted file mode 100644 index 755c326e3..000000000 --- a/website/src/content/docs/docs/getting-started/first-plugin.mdx +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Write your first plugin -description: Install a small echo plugin and verify that Bub uses it. -sidebar: - order: 4 ---- - -A plugin changes how the Bub app handles a task. This one replaces the model stage with an echo response, so you can check plugin discovery and turn processing without model credentials. To use the builtin agent's model calls and conversation recording, keep its model implementation. - -## Before you begin - -Install Python 3.12+ and uv as described in [Install Bub](/docs/getting-started/install/). This tutorial creates a project environment containing Bub 0.6.0 and your plugin. - -## Create the package - -Create this package layout: - -```text -bub-echo-plugin/ -├── pyproject.toml -└── src/ - └── bub_echo_plugin/ - ├── __init__.py - └── plugin.py -``` - -Leave `__init__.py` empty. Put this in `pyproject.toml`: - -```toml -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "bub-echo-plugin" -version = "0.1.0" -requires-python = ">=3.12" -dependencies = ["bub[app]>=0.6.0"] - -[project.entry-points."bub"] -echo = "bub_echo_plugin.plugin:echo_plugin" - -[tool.hatch.build.targets.wheel] -packages = ["src/bub_echo_plugin"] -``` - -The `bub` entry-point group tells the application where to find the plugin. - -## Implement the response - -Put this in `src/bub_echo_plugin/plugin.py`: - -```python -from bub import hookimpl - - -class EchoPlugin: - @hookimpl - def run_model(self, prompt, session_id, state): - return f"[echo:{session_id}] {prompt}" - - -echo_plugin = EchoPlugin() -``` - -The application still builds the incoming prompt. Your `run_model` implementation supplies the response instead of calling the builtin agent. - -## Install and try it - -From the plugin directory, install the project and its dependencies: - -```bash -uv sync -uv run bub hooks -``` - -`uv sync` installs this package in editable mode. Run Bub through `uv run` in the same project so it can discover the plugin. - -The report should include `echo` under `run_model`. The builtin agent appears under `run_model_stream`. - -```bash -uv run bub run "hello from my plugin" -``` - -With no other plugin overriding the model stage, the response contains: - -```text -[echo:cli:local] hello from my plugin -``` - -If `echo` is missing, check that the package was installed in the environment running `bub`. If it is listed but another response wins, inspect the other model hooks and their [priority](/docs/reference/hooks/#iteration-order). - -- [Package a plugin](/docs/build/plugins/) explains discovery and entry-point choices. -- [Change application behavior](/docs/build/hooks/) helps you choose a hook for your task. diff --git a/website/src/content/docs/docs/getting-started/first-skill.mdx b/website/src/content/docs/docs/getting-started/first-skill.mdx deleted file mode 100644 index 4b363f3ab..000000000 --- a/website/src/content/docs/docs/getting-started/first-skill.mdx +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: Add a project skill -description: Give Bub a reusable procedure and check that it can load it. -sidebar: - order: 3 ---- - -A skill gives Bub instructions for a particular task. In this example, you will ask it to inspect a repository before changing code. - -Use the workspace from [Run Bub for the first time](/docs/getting-started/first-turn/). A skill contains a `SKILL.md` file and can include scripts or reference material alongside it. - -## Write the instructions - -Create a directory for the skill: - -```bash -mkdir -p .agents/skills/repo-map -``` - -Create `.agents/skills/repo-map/SKILL.md`: - -```markdown ---- -name: repo-map -description: Read the local repository layout before changing code. ---- - -1. Run `rg --files` to see the repository layout. -2. Read the files that explain the requested change. -3. Describe the affected modules before editing code. -``` - -The `name` must match the directory name. Use lowercase words separated by hyphens. The description helps the model decide when to use the skill. - -## Check that Bub can load it - -```bash -bub run ",skill name=repo-map" -``` - -The command prints the skill's instructions without calling a model. If it returns `(no such skill)`, check the path and the YAML frontmatter. - -## Use it in a conversation - -With your model configured, run: - -```bash -bub run 'Use the $repo-map skill, then list the top-level modules you found.' -``` - -`$repo-map` includes the skill's instructions in this turn's system prompt. The model then follows them using the tools available in the application. Install `rg` in the execution environment for this example. - -You can keep this skill in the project or [package it for other users](/docs/build/skills/). The packaging guide also explains discovery order and support files. diff --git a/website/src/content/docs/docs/getting-started/index.mdx b/website/src/content/docs/docs/getting-started/index.mdx index b10817d7e..89875b86c 100644 --- a/website/src/content/docs/docs/getting-started/index.mdx +++ b/website/src/content/docs/docs/getting-started/index.mdx @@ -7,17 +7,17 @@ sidebar: Put an agent in your own Python program, or start with the Bub application and customize it. The application uses the same agent kernel and adds configuration, plugins, and channel integration. -## Build your own agent +## Use the Bub app -Choose a model and tools, assemble an agent with `bub.core.Agent`, and connect it to your program. Start by defining a tool that looks up an order, then let the agent call it. +Run Bub, then use configuration and plugins to add the behavior you need. This route also covers skills, channels, and deployment. -[Build your own agent](/docs/build/sdk/) takes you through the Python example. When you need an HTTP endpoint, continue with [FastAPI](/docs/tutorials/fastapi/). +[Run your first conversation](/docs/app/first-session/) gets the app running. [Use the Bub app](/docs/app/) helps you choose what to extend next. -## Customize the Bub app +## Build your own Bub Agent -Run Bub, then use configuration and plugins to add the behavior you need. This route also covers skills, channels, and deployment. +Choose a model and tools, assemble an agent with `bub.core.Agent`, and connect it to your program. Start by defining a tool that looks up an order, then let the agent call it. -[Run your first conversation](/docs/getting-started/first-turn/) gets the app running. [Customize the Bub app](/docs/build/) helps you choose what to extend next. +[Build your own Bub Agent](/docs/sdk/) takes you through the Python example. When you need an HTTP endpoint, continue with [FastAPI](/docs/sdk/web-service/). ## Read the common explanations as you need them diff --git a/website/src/content/docs/docs/getting-started/install.mdx b/website/src/content/docs/docs/getting-started/install.mdx index 26a89f40b..6159d1a7f 100644 --- a/website/src/content/docs/docs/getting-started/install.mdx +++ b/website/src/content/docs/docs/getting-started/install.mdx @@ -22,7 +22,7 @@ uv run python -c "from bub.core import Agent; print(Agent.__name__)" If you are starting a new project, first run `uv init --python 3.12 my-agent` and `cd my-agent`. -The base installation includes the kernel's dependencies: Republic, Pydantic, Loguru, and typing-extensions. The import check needs no model credentials. Continue with [Build your own agent](/docs/build/sdk/) to configure a model in Python and run the first example. +The base installation includes the kernel's dependencies: Republic, Pydantic, Loguru, and typing-extensions. The import check needs no model credentials. Continue with [Build your own agent](/docs/sdk/) to configure a model in Python and run the first example. @@ -65,7 +65,7 @@ When installation finishes, make sure the command directory is on your PATH, the bub --help ``` -Continue with [Run Bub for the first time](/docs/getting-started/first-turn/) to start a conversation in your own workspace. +Continue with [Run Bub for the first time](/docs/app/first-session/) to start a conversation in your own workspace. The base install and `bub[app]` contain the same package files. The extra changes which dependencies are installed. Without the application dependencies, the `bub` command exits with an instruction to install `bub[app]`. diff --git a/website/src/content/docs/docs/tutorials/acp-server.mdx b/website/src/content/docs/docs/integrations/acp.mdx similarity index 93% rename from website/src/content/docs/docs/tutorials/acp-server.mdx rename to website/src/content/docs/docs/integrations/acp.mdx index b69a6dba1..223fc1b80 100644 --- a/website/src/content/docs/docs/tutorials/acp-server.mdx +++ b/website/src/content/docs/docs/integrations/acp.mdx @@ -2,6 +2,7 @@ title: Connect an ACP editor description: Install bub-acp-server and connect Bub to an ACP-compatible editor such as Zed. sidebar: + badge: { text: 'App', variant: 'note' } order: 6 --- @@ -128,6 +129,6 @@ When the editor reopens an existing thread, `bub-acp-server` loads the matching ## Next steps -- [Configure](/docs/operate/configure/): Bub's model and runtime settings. -- [Turn pipeline](/docs/concepts/turn-pipeline/): how Bub turns inbound messages into model calls and outbound messages. -- [Build plugins](/docs/build/plugins/): extend Bub with hooks, tools, and channels. +- [Configure](/docs/app/configure/): Bub's model and runtime settings. +- [Turn pipeline](/docs/concepts/turn/): how Bub turns inbound messages into model calls and outbound messages. +- [Build plugins](/docs/app/plugins/): extend Bub with hooks, tools, and channels. diff --git a/website/src/content/docs/docs/tutorials/agent-plugins.mdx b/website/src/content/docs/docs/integrations/agent-plugins.mdx similarity index 95% rename from website/src/content/docs/docs/tutorials/agent-plugins.mdx rename to website/src/content/docs/docs/integrations/agent-plugins.mdx index 858fd0787..c5ecb9453 100644 --- a/website/src/content/docs/docs/tutorials/agent-plugins.mdx +++ b/website/src/content/docs/docs/integrations/agent-plugins.mdx @@ -2,6 +2,7 @@ title: Load Agent Plugins description: Load a portable Agent Plugin with a skill and an MCP server, and use it alongside existing Bub integrations. sidebar: + badge: { text: 'App', variant: 'note' } order: 4 --- @@ -11,7 +12,7 @@ MCP server from one portable package, then verify both in Bub. The integration lives in [`bub-contrib`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-agent-plugins). -Bub's Python plugins still use the [Python plugin entry points](/docs/build/plugins/). +Bub's Python plugins still use the [Python plugin entry points](/docs/app/plugins/). ## Before you begin @@ -129,7 +130,7 @@ the tool. The first launch can take longer while `uvx` downloads the time server `command not found` error, the tool has not registered yet; check startup logs for connection errors and retry after startup completes. -Once you have [configured a model](/docs/operate/configure/), you can also ask Bub to call the time tool or +Once you have [configured a model](/docs/app/configure/), you can also ask Bub to call the time tool or summarize notes with `$plugin-summary`. ## Use it alongside existing MCP servers @@ -212,6 +213,6 @@ Download or update portable packages on disk, then restart Bub to load the chang ## Next steps -- [Connect MCP servers](/docs/tutorials/mcp/): manage servers in Bub's home configuration. -- [Build skills](/docs/build/skills/): write and validate skills for Bub. -- [Build plugins](/docs/build/plugins/): extend Bub's hooks with a Python package. +- [Connect MCP servers](/docs/integrations/mcp/): manage servers in Bub's home configuration. +- [Build skills](/docs/app/skills/): write and validate skills for Bub. +- [Build plugins](/docs/app/plugins/): extend Bub's hooks with a Python package. diff --git a/website/src/content/docs/docs/integrations/index.mdx b/website/src/content/docs/docs/integrations/index.mdx new file mode 100644 index 000000000..6d87f40f4 --- /dev/null +++ b/website/src/content/docs/docs/integrations/index.mdx @@ -0,0 +1,26 @@ +--- +title: Connect Bub to your tools +description: Add an HTTP service, an editor, a model server, or an application integration. +sidebar: + order: 0 +--- + +Choose an integration for the way you use Bub. Each guide names the components it needs and gives you a way to check the result. + +## Build your own agent + +- [Serve an agent with FastAPI](/docs/sdk/web-service/): expose `core.Agent` over HTTP and manage conversation storage in your service. + +## Customize the Bub app + +These guides use the app's CLI, configuration, or plugin system. Start with the [app installation](/docs/getting-started/install/) and use the same environment for Bub and its plugins. + +- [Connect MCP servers](/docs/integrations/mcp/): call tools supplied by an MCP server. +- [Load Agent Plugins](/docs/integrations/agent-plugins/): load a portable package of skills and MCP servers. +- [Connect an ACP editor](/docs/integrations/acp/): use Bub from an editor such as Zed. +- [Use a local llama.cpp model](/docs/integrations/local-models/): point the app at a local model server. +- [Store conversation history in SQLite](/docs/integrations/storage/): replace the app's file store through a plugin. +- [Inspect runs with tapes and traces](/docs/integrations/observability/): inspect local records or export traces to Phoenix and Logfire. +- [Implement hooks in another language](/docs/app/language-extensions/): try the experimental WebAssembly integration. + +Each integration guide links to the plugin's documentation for package-specific setup. diff --git a/website/src/content/docs/docs/tutorials/local-llama-cpp.mdx b/website/src/content/docs/docs/integrations/local-models.mdx similarity index 99% rename from website/src/content/docs/docs/tutorials/local-llama-cpp.mdx rename to website/src/content/docs/docs/integrations/local-models.mdx index b823ad610..30f5c3fcf 100644 --- a/website/src/content/docs/docs/tutorials/local-llama-cpp.mdx +++ b/website/src/content/docs/docs/integrations/local-models.mdx @@ -2,6 +2,7 @@ title: Run Bub with a local llama.cpp model description: Start a local llama.cpp server and configure Bub to use it as an OpenAI-compatible model provider. sidebar: + badge: { text: 'Both', variant: 'success' } order: 3 --- diff --git a/website/src/content/docs/docs/tutorials/mcp.mdx b/website/src/content/docs/docs/integrations/mcp.mdx similarity index 91% rename from website/src/content/docs/docs/tutorials/mcp.mdx rename to website/src/content/docs/docs/integrations/mcp.mdx index 63ff5b38c..9dc0e20df 100644 --- a/website/src/content/docs/docs/tutorials/mcp.mdx +++ b/website/src/content/docs/docs/integrations/mcp.mdx @@ -2,12 +2,13 @@ title: Connect MCP servers description: Install the bub-mcp plugin, register a time MCP server, and call its tools from a Bub turn. sidebar: + badge: { text: 'App', variant: 'note' } order: 2 --- Connect an MCP server to the Bub application with the `bub-mcp` plugin. This guide registers a time server, checks the connection, and calls one of its tools. -The integration uses the app's plugin system and lifecycle. For tool definitions shared with direct Agent use, see [Tools](/docs/build/tools/). +The integration uses the app's plugin system and lifecycle. For tool definitions shared with direct Agent use, see [Tools](/docs/sdk/tools/). ## Before you begin @@ -27,7 +28,7 @@ bub install bub-mcp@main `bub install` uses the active Bub environment. See the [CLI reference](/docs/reference/cli/#bub-install) for plugin source and dependency handling. -[Agent Plugins](/docs/tutorials/agent-plugins/) can also load skills and their own MCP servers alongside these servers. That integration uses a separate lifecycle channel and plugin-qualified tool names. +[Agent Plugins](/docs/integrations/agent-plugins/) can also load skills and their own MCP servers alongside these servers. That integration uses a separate lifecycle channel and plugin-qualified tool names. Verify the plugin loaded: @@ -108,7 +109,7 @@ Wait a few seconds after `channel.manager started listening` so the MCP bootstra If the model answers without calling the tool, check that server startup has finished, then ask it explicitly to use the time tool. MCP startup runs in the background, so the first turn can begin before the tools are available. -For long-running deployments, see [Deploy](/docs/operate/deploy/); the same gateway invocation is what runs in the container image. +For long-running deployments, see [Deploy](/docs/app/deploy/); the same gateway invocation is what runs in the container image. ## Add other server types @@ -182,6 +183,6 @@ bub mcp remove time ## Next steps -- [Load Agent Plugins](/docs/tutorials/agent-plugins/): load portable skills and MCP servers alongside `mcp.json`. -- [Configure](/docs/operate/configure/): Bub's config layer and environment variables. +- [Load Agent Plugins](/docs/integrations/agent-plugins/): load portable skills and MCP servers alongside `mcp.json`. +- [Configure](/docs/app/configure/): Bub's config layer and environment variables. - [bub-mcp source](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-mcp): the plugin's full source and advanced options. diff --git a/website/src/content/docs/docs/tutorials/observability.mdx b/website/src/content/docs/docs/integrations/observability.mdx similarity index 97% rename from website/src/content/docs/docs/tutorials/observability.mdx rename to website/src/content/docs/docs/integrations/observability.mdx index c0bed583a..c084bc133 100644 --- a/website/src/content/docs/docs/tutorials/observability.mdx +++ b/website/src/content/docs/docs/integrations/observability.mdx @@ -2,6 +2,7 @@ title: Inspect app runs description: Inspect conversation records and export model and tool traces to Phoenix or Logfire. sidebar: + badge: { text: 'Both', variant: 'success' } order: 1 --- @@ -54,7 +55,7 @@ The fields are useful when the model starts behaving oddly: - `last_token_usage` appears when token usage has been recorded by the model path. - `last_token_cache_hit_rate` shows the cached share of prompt tokens for that same model call when available. -The tape stores conversation entries and selected run records. The context selector decides which entries become model input; `,tape.info` and `,tape.search` let you inspect the stored record. See [Tape and context](/docs/concepts/tape-and-context/) for the persistence boundary. +The tape stores conversation entries and selected run records. The context selector decides which entries become model input; `,tape.info` and `,tape.search` let you inspect the stored record. See [Tape and context](/docs/concepts/tape/) for the persistence boundary. ## 2. Search the tape for symptoms @@ -198,4 +199,4 @@ docker rm -f bub-phoenix ## Next steps -- [Tape and context](/docs/concepts/tape-and-context/): understand what Bub records and how context is rebuilt. +- [Tape and context](/docs/concepts/tape/): understand what Bub records and how context is rebuilt. diff --git a/website/src/content/docs/docs/tutorials/tapestore-sqlalchemy.mdx b/website/src/content/docs/docs/integrations/storage.mdx similarity index 88% rename from website/src/content/docs/docs/tutorials/tapestore-sqlalchemy.mdx rename to website/src/content/docs/docs/integrations/storage.mdx index 385b1f3b2..6278da350 100644 --- a/website/src/content/docs/docs/tutorials/tapestore-sqlalchemy.mdx +++ b/website/src/content/docs/docs/integrations/storage.mdx @@ -2,12 +2,13 @@ title: Store app history in SQLite description: Replace the Bub application's file store with SQLite through a tape store plugin. sidebar: + badge: { text: 'Both', variant: 'success' } order: 2 --- Store the Bub application's conversation history in a local SQLite database with [`bub-tapestore-sqlalchemy`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-tapestore-sqlalchemy). The plugin replaces the app's default file store; you can continue using `,tape.info` and `,tape.search` to inspect records. -This guide configures the plugin for the Bub CLI through `BUB_*` settings. In your own Python program, you can construct `SQLAlchemyTapeStore` and pass it directly to `core.Agent`; see [Reuse plugins in your agent](/docs/build/reuse-plugins/). [Tape and context](/docs/concepts/tape-and-context/) explains the shared storage behavior. +This guide configures the plugin for the Bub CLI through `BUB_*` settings. In your own Python program, you can construct `SQLAlchemyTapeStore` and pass it directly to `core.Agent`; see [Reuse plugins in your agent](/docs/sdk/app-plugins/). [Tape and context](/docs/concepts/tape/) explains the shared storage behavior. ## Before you begin @@ -110,6 +111,6 @@ rm -f "$PWD/bub-tapes.db" ## Next steps -- [Observe Bub with tapes and Phoenix](/docs/tutorials/observability/): pair the durable tape with GenAI telemetry. -- [Tape and context](/docs/concepts/tape-and-context/): what Bub records and how context is rebuilt. -- [Plugins](/docs/build/plugins/): the plugin contract `bub-tapestore-sqlalchemy` follows. +- [Observe Bub with tapes and Phoenix](/docs/integrations/observability/): pair the durable tape with GenAI telemetry. +- [Tape and context](/docs/concepts/tape/): what Bub records and how context is rebuilt. +- [Plugins](/docs/app/plugins/): the plugin contract `bub-tapestore-sqlalchemy` follows. diff --git a/website/src/content/docs/docs/operate/index.mdx b/website/src/content/docs/docs/operate/index.mdx deleted file mode 100644 index c4ff58433..000000000 --- a/website/src/content/docs/docs/operate/index.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Run the Bub app -description: Configure your Bub application, connect a channel, and deploy it as a service. -sidebar: - order: 0 ---- - -Once you have [run your first conversation](/docs/getting-started/first-turn/), choose how you want to use Bub day to day. Keep it in the terminal, connect a Telegram bot, or run a gateway on a server. - -These guides use the Bub application and its configuration, plugins, and channels. Install the application dependencies using the [installation guide](/docs/getting-started/install/). - -## Choose your next step - -- [Configure the app](/docs/operate/configure/): select a model and set provider credentials. -- [Connect channels](/docs/operate/channels/): choose which services receive messages and how they group them into turns. -- [Use the terminal](/docs/operate/channels/cli/): run one task or continue a conversation. -- [Connect Telegram](/docs/operate/channels/telegram/): set up a bot and choose who can use it. -- [Deploy the app](/docs/operate/deploy/): build the container and keep its data across restarts. -- [Inspect a run](/docs/tutorials/observability/): check conversation history and export execution traces. - -For a service that directly owns a `core.Agent`, follow [Serve an agent with FastAPI](/docs/tutorials/fastapi/). That route manages models and storage in Python. diff --git a/website/src/content/docs/docs/reference/cli.mdx b/website/src/content/docs/docs/reference/cli.mdx index 4cc6fc5fb..7d542c902 100644 --- a/website/src/content/docs/docs/reference/cli.mdx +++ b/website/src/content/docs/docs/reference/cli.mdx @@ -21,7 +21,7 @@ The framework is created in `bub/__main__.py`, which calls `BubFramework().load_ Running `bub` without a command runs `BubFramework.default_command` with its defaults. The builtin plugin sets it to `chat`, so `bub` and `bub -w PATH` start the chat REPL. A plugin can choose another command by setting `default_command` in its `register_cli_commands` hook; the builtin only fills it when no plugin has. -Terminals that support [OSC 7501](https://www.superlogical.com/rex/docs/build/program-status) can show when Bub is working, waiting for input or approval, finished, or failed. Cancelling a command reports `idle`. `chat` and `gateway` report `idle` once listening, and remain busy until all accepted messages finish. Other terminals display normal command output. Redirected output contains no status sequences. +Terminals that support [OSC 7501](https://www.superlogical.com/rex/docs/app/program-status) can show when Bub is working, waiting for input or approval, finished, or failed. Cancelling a command reports `idle`. `chat` and `gateway` report `idle` once listening, and remain busy until all accepted messages finish. Other terminals display normal command output. Redirected output contains no status sequences. ## `bub run` @@ -92,7 +92,7 @@ Behavior notes: - `--uninstall` stops and removes the corresponding systemd user unit or Windows scheduled task. It succeeds when the service is already absent and does not require an enabled channel. - `--install` and `--uninstall` are mutually exclusive. -See [Operate › Channels](/docs/operate/channels/) for per-channel deployment notes. +See [Operate › Channels](/docs/app/gateway/) for per-channel deployment notes. ## `bub onboard` diff --git a/website/src/content/docs/docs/reference/hooks.mdx b/website/src/content/docs/docs/reference/hooks.mdx index 30db239e8..93bfc5b85 100644 --- a/website/src/content/docs/docs/reference/hooks.mdx +++ b/website/src/content/docs/docs/reference/hooks.mdx @@ -3,7 +3,7 @@ title: Application hooks description: Hook signatures, dispatch rules, and resource lifecycles for Bub plugins. --- -Hooks connect plugins to the Bub application. This reference lists the 26 hooks declared by `BubHookSpecs`. For examples, see [Change application behavior](/docs/build/hooks/); for the order of application stages, see [The application turn](/docs/concepts/turn-pipeline/). +Hooks connect plugins to the Bub application. This reference lists the 26 hooks declared by `BubHookSpecs`. For examples, see [Change application behavior](/docs/app/hooks/); for the order of application stages, see [The application turn](/docs/concepts/turn/). `first` below means the first non-`None` result in hook priority order. `all` means every implementation is called. An ordinary hook exception stops that dispatch and propagates; the [agent interception hooks](#agent-loop-hooks-pluginagenthooks) and `on_error` have separate exception handling. @@ -85,7 +85,7 @@ A provider that needs cleanup can yield once and release its resource in `finall The builtin lifespan owns background shell processes started within that scope. On POSIX, shutdown targets their process groups; Windows currently targets direct shell processes. Processes outside those groups and forced process termination require host-level supervision. -Tape sidecar providers contribute named sibling tapes. Bub mounts them on the application's root Tape and applies fork, merge, archive, and reset to mounted sidecars. Stored data remains after a provider is removed. See [Tape and context](/docs/concepts/tape-and-context/) for shared storage semantics. +Tape sidecar providers contribute named sibling tapes. Bub mounts them on the application's root Tape and applies fork, merge, archive, and reset to mounted sidecars. Stored data remains after a provider is removed. See [Tape and context](/docs/concepts/tape/) for shared storage semantics. diff --git a/website/src/content/docs/docs/reference/index.mdx b/website/src/content/docs/docs/reference/index.mdx index 828b82aad..c5ead7d79 100644 --- a/website/src/content/docs/docs/reference/index.mdx +++ b/website/src/content/docs/docs/reference/index.mdx @@ -11,11 +11,11 @@ Look up the interface used by your code. The Agent interfaces and application ex | Interface | Reference | | --- | --- | -| Agent construction and model clients | [Agent parameters](/docs/build/sdk/#agent-parameters) and [models](/docs/build/sdk/#models) | -| Runs and stream ownership | [Streams and lifecycle](/docs/build/sdk/#streams-and-lifecycle) | -| Prompt sections and loop customization | [System prompt](/docs/build/sdk/#system-prompt) and [customize the loop](/docs/build/sdk/#customize-the-loop) | -| Tools and CodeMode | [Tools](/docs/build/tools/) | -| Tape, storage, and context | [Tape and context](/docs/concepts/tape-and-context/) | +| Agent construction and model clients | [Agent parameters](/docs/sdk/#agent-parameters) and [models](/docs/sdk/#models) | +| Runs and stream ownership | [Streams and lifecycle](/docs/sdk/#streams-and-lifecycle) | +| Prompt sections and loop customization | [System prompt](/docs/sdk/#system-prompt) and [customize the loop](/docs/sdk/#customize-the-loop) | +| Tools and CodeMode | [Tools](/docs/sdk/tools/) | +| Tape, storage, and context | [Tape and context](/docs/concepts/tape/) | | Event delivery and decisions | [Agent events](/docs/concepts/events/) | diff --git a/website/src/content/docs/docs/reference/settings.mdx b/website/src/content/docs/docs/reference/settings.mdx index fc30d92b2..331a4e219 100644 --- a/website/src/content/docs/docs/reference/settings.mdx +++ b/website/src/content/docs/docs/reference/settings.mdx @@ -3,7 +3,7 @@ title: Application settings description: Built-in application environment variables, configuration keys, and defaults. --- -These settings configure the Bub application. The tables cover built-in `BUB_*` variables and their YAML keys; plugins document their own settings. For setup steps, see [Configure the app](/docs/operate/configure/). A directly constructed `core.Agent` receives its components as Python arguments and does not load this configuration. +These settings configure the Bub application. The tables cover built-in `BUB_*` variables and their YAML keys; plugins document their own settings. For setup steps, see [Configure the app](/docs/app/configure/). A directly constructed `core.Agent` receives its components as Python arguments and does not load this configuration. ## Precedence @@ -176,7 +176,7 @@ Loaded under the YAML `telegram:` section. | `BUB_TELEGRAM_ALLOW_CHATS` | unset | `allow_chats` | Comma-separated allowlist of Telegram chat IDs. Empty means no restriction. | | `BUB_TELEGRAM_PROXY` | unset | `proxy` | Proxy URL for the Telegram API, e.g. `http://user:pass@host:port` or `socks5://host:port`. | -See [Operate › Channels › Telegram](/docs/operate/channels/telegram/) for deployment notes. +See [Operate › Channels › Telegram](/docs/app/telegram/) for deployment notes. ## Login: third-party env vars @@ -188,4 +188,4 @@ See [Operate › Channels › Telegram](/docs/operate/channels/telegram/) for de ## Plugin-specific settings -Plugins can register their own `Settings` subclass via the `@config(name="...")` decorator (see [Build › Plugins](/docs/build/plugins/)). The decorator records the class under `CONFIG_MAP[name]`, which `configure.validate` then validates and `ensure_config` reads. The YAML key matches the registered name; env vars follow whatever `env_prefix` the subclass declares. +Plugins can register their own `Settings` subclass via the `@config(name="...")` decorator (see [Build › Plugins](/docs/app/plugins/)). The decorator records the class under `CONFIG_MAP[name]`, which `configure.validate` then validates and `ensure_config` reads. The YAML key matches the registered name; env vars follow whatever `env_prefix` the subclass declares. diff --git a/website/src/content/docs/docs/reference/types.mdx b/website/src/content/docs/docs/reference/types.mdx index 267c6cf2f..85b77374f 100644 --- a/website/src/content/docs/docs/reference/types.mdx +++ b/website/src/content/docs/docs/reference/types.mdx @@ -3,7 +3,7 @@ title: Application types and lifecycle description: Message, turn, stream, and channel interfaces used by the Bub application. --- -Use these types when writing a plugin, adding a channel, or embedding the Bub application. Shared agent capabilities are covered in [Tools](/docs/build/tools/), [Tape and context](/docs/concepts/tape-and-context/), and [Events](/docs/concepts/events/). +Use these types when writing a plugin, adding a channel, or embedding the Bub application. Shared agent capabilities are covered in [Tools](/docs/sdk/tools/), [Tape and context](/docs/concepts/tape/), and [Events](/docs/concepts/events/). ## Messages and turn results @@ -135,7 +135,7 @@ from bub.builtin.agent import BubAgent Application configuration is process-wide. Construct a `BubAgent` inside `framework.running()` when it should use the framework's active store. An explicitly supplied store takes precedence and remains the caller's lifecycle responsibility. Without either store, the agent uses memory. -`run_stream` loads initial state when the caller omits `state` and uses caller-supplied state directly otherwise. The host owns save-state hooks, outbound handling, and concurrent turn scheduling. See [Embed the application](/docs/build/distribution/#programmatic-embedding) for complete usage. +`run_stream` loads initial state when the caller omits `state` and uses caller-supplied state directly otherwise. The host owns save-state hooks, outbound handling, and concurrent turn scheduling. See [Embed the application](/docs/app/package/#programmatic-embedding) for complete usage. ## Imports diff --git a/website/src/content/docs/docs/reference/versions.mdx b/website/src/content/docs/docs/reference/versions.mdx index 3f6ad3675..5986bcb8d 100644 --- a/website/src/content/docs/docs/reference/versions.mdx +++ b/website/src/content/docs/docs/reference/versions.mdx @@ -21,13 +21,13 @@ uv pip install --upgrade "bub[app]" In a plugin or distribution's `pyproject.toml`, use `bub[app]>=0.6.0` in place of a plain `bub` dependency. The extra installs the dependencies required by the builtin runtime, Framework, settings, plugin hooks, skills, and channels. -Run `bub --help`, then [run a conversation](/docs/getting-started/first-turn/). Use `bub hooks` when checking that your plugins loaded. +Run `bub --help`, then [run a conversation](/docs/app/first-session/). Use `bub hooks` when checking that your plugins loaded. ## Choose the interfaces your code uses | Your code | Dependency and guide | | --- | --- | -| Supplies a model and tools directly to `core.Agent` | `bub`; [Build your own agent](/docs/build/sdk/) | -| Uses `BubAgent`, the Framework, application hooks, or channels | `bub[app]`; [Customize the Bub app](/docs/build/) | +| Supplies a model and tools directly to `core.Agent` | `bub`; [Build your own agent](/docs/sdk/) | +| Uses `BubAgent`, the Framework, application hooks, or channels | `bub[app]`; [Customize the Bub app](/docs/app/) | -A directly composed Agent takes its model and components in Python. The Bub application supplies its settings, skills, and tool policies through `BubAgent` and the Framework. [Tools](/docs/build/tools/) and [Agent events](/docs/concepts/events/) explain the shared contracts and the differences in default behavior. +A directly composed Agent takes its model and components in Python. The Bub application supplies its settings, skills, and tool policies through `BubAgent` and the Framework. [Tools](/docs/sdk/tools/) and [Agent events](/docs/concepts/events/) explain the shared contracts and the differences in default behavior. diff --git a/website/src/content/docs/docs/build/reuse-plugins.mdx b/website/src/content/docs/docs/sdk/app-plugins.mdx similarity index 95% rename from website/src/content/docs/docs/build/reuse-plugins.mdx rename to website/src/content/docs/docs/sdk/app-plugins.mdx index da279704f..1db8d3ae9 100644 --- a/website/src/content/docs/docs/build/reuse-plugins.mdx +++ b/website/src/content/docs/docs/sdk/app-plugins.mdx @@ -66,7 +66,7 @@ if __name__ == "__main__": Run `uv run python search_agent.py`. The plugin handles the search request and its HTTP connection. The Agent records the conversation under `sessions/`, and your code consumes the reply. -For an existing Agent, add the returned tool with `agent.tools[search.name] = search` during startup. Its name is `web.search`; the model sees `web_search`. You can keep the rest of your calling code, including a [FastAPI endpoint](/docs/tutorials/fastapi/). +For an existing Agent, add the returned tool with `agent.tools[search.name] = search` during startup. Its name is `web.search`; the model sees `web_search`. You can keep the rest of your calling code, including a [FastAPI endpoint](/docs/sdk/web-service/). `register_tools()` also updates the process-wide tool registry, so do this setup during startup. An Agent uses the tools you explicitly pass or add to it. This example takes the returned search tool; add the package's other tools, such as Jina's `web.read`, separately if needed. @@ -101,7 +101,7 @@ The adapter connects this plugin's `before_llm_call` and `after_llm_call` hooks Set the context limit with `BUB_CONTEXT_BUDGET_MAX_CONTEXT_TOKENS` or `context-budget.max_context_tokens` in `bub.yml`. The plugin reserves 16,384 tokens from that limit before requesting a handoff. The budget is advisory: execution depends on the model following the instruction. -A handoff saves its summary on the anchor. To carry that summary into the next model request, include it through your [context selection](/docs/concepts/tape-and-context/#context) or next prompt. +A handoff saves its summary on the anchor. To carry that summary into the next model request, include it through your [context selection](/docs/concepts/tape/#context) or next prompt. Register and attach once during service startup. This plugin runs entirely through call hooks. Registration belongs to the Framework, and `attach()` connects its agent-loop hooks to the selected Agent's events. Every session using that Agent shares those hooks. Separate Framework instances are needed for different plugin sets; configuration remains process-wide. @@ -147,7 +147,7 @@ The plugin's implementation determines what your host must provide: | --- | --- | | Tools or a store | Import the public component and pass it to Agent. For example, `bub_tapestore_sqlalchemy.store.SQLAlchemyTapeStore` accepts a database URL and can be passed as `store=`. | | Model or tool interception, system prompt, continuation, or steering | Register the selected plugin and [attach `AgentPlugins`](#agent-plugins). Check any state fields or tools the plugin expects. | -| Message preparation, model-stage replacement, state saving, or delivery | [Embed the application pipeline](/docs/build/distribution/#programmatic-embedding) in your host. It runs these stages and returns a `TurnResult`. | +| Message preparation, model-stage replacement, state saving, or delivery | [Embed the application pipeline](/docs/app/package/#programmatic-embedding) in your host. It runs these stages and returns a `TurnResult`. | | Background connections or discovered tools | Open the service, wait until it is ready, attach its tools, then close it with the host. Follow the plugin's own lifecycle API. | Follow the chosen store's lifecycle contract. `framework.running()` acquires hook-provided resources and closes the context managers it entered. A store returned as an ordinary object keeps its own cleanup requirements. @@ -168,6 +168,6 @@ The direct stream call bypasses the initial `build_prompt`, `run_model` / `run_m ## Keep building -- [Tools and CodeMode](/docs/build/tools/) explains how Agent uses the tools you supply. -- [Tape and context](/docs/concepts/tape-and-context/) covers storage and handoffs. -- [Package a plugin](/docs/build/plugins/) shows how to share your own extension. +- [Tools and CodeMode](/docs/sdk/tools/) explains how Agent uses the tools you supply. +- [Tape and context](/docs/concepts/tape/) covers storage and handoffs. +- [Package a plugin](/docs/app/plugins/) shows how to share your own extension. diff --git a/website/src/content/docs/docs/build/sdk.md b/website/src/content/docs/docs/sdk/index.md similarity index 93% rename from website/src/content/docs/docs/build/sdk.md rename to website/src/content/docs/docs/sdk/index.md index da3a28714..ec5d3df7b 100644 --- a/website/src/content/docs/docs/build/sdk.md +++ b/website/src/content/docs/docs/sdk/index.md @@ -7,7 +7,7 @@ sidebar: Choose a model, add tools, and connect the agent to your program. `bub.core.Agent` runs the model and tool loop while you decide how to store conversations and manage the surrounding application. -You supply the agent's components directly. The [Bub application](/docs/build/) builds on the same core and adds configuration and plugin discovery. You can also [reuse application plugins](/docs/build/reuse-plugins/) in your own service while keeping its existing entry points and calling code. +You supply the agent's components directly. The [Bub application](/docs/app/) builds on the same core and adds configuration and plugin discovery. You can also [reuse application plugins](/docs/sdk/app-plugins/) in your own service while keeping its existing entry points and calling code. ## Install @@ -84,7 +84,7 @@ Run the program again with the same `tape` name to continue the conversation sto `agent.run()` yields a stream. Iterating the stream drives the loop: call the model, run its requested tools, then call the model again until it answers without further tool calls. A `final` event ends one model step; exhaust the stream to finish the run. -Keep the stream inside `async with`. Leaving the block closes it after normal completion, an exception, or an early `break`. Closing also releases the open model stream and merges the run's tape fork. Records already written are kept even when the run fails; see [Forks and partial results](/docs/concepts/tape-and-context/#forks). +Keep the stream inside `async with`. Leaving the block closes it after normal completion, an exception, or an early `break`. Closing also releases the open model stream and merges the run's tape fork. Records already written are kept even when the run fails; see [Forks and partial results](/docs/concepts/tape/#forks). If another component will consume the stream, `await agent.start(...)` returns it with the same arguments. That component must close it, including when it never starts iteration. Prefer `run()` when you own the consuming code. @@ -142,7 +142,7 @@ def customer(context: PromptContext) -> str | None: -The `state` passed to a run is a mutable dictionary shared by tools, prompt sections, and event handlers. You decide which values to save and how to restore them for another run. See [Turn state](/docs/concepts/tape-and-context/#state) for what to keep between runs. +The `state` passed to a run is a mutable dictionary shared by tools, prompt sections, and event handlers. You decide which values to save and how to restore them for another run. See [Turn state](/docs/concepts/tape/#state) for what to keep between runs. @@ -202,6 +202,6 @@ For a tape whose lifecycle you manage, open `agent.session(name, state=...)` and ## Keep building -- [Add tools and CodeMode](/docs/build/tools/) to give your agent actions. -- [Store conversation history](/docs/concepts/tape-and-context/) to continue work across runs. -- [Serve your agent with FastAPI](/docs/tutorials/fastapi/) when another program needs an HTTP endpoint. +- [Add tools and CodeMode](/docs/sdk/tools/) to give your agent actions. +- [Store conversation history](/docs/concepts/tape/) to continue work across runs. +- [Serve your agent with FastAPI](/docs/sdk/web-service/) when another program needs an HTTP endpoint. diff --git a/website/src/content/docs/docs/build/tools.mdx b/website/src/content/docs/docs/sdk/tools.mdx similarity index 92% rename from website/src/content/docs/docs/build/tools.mdx rename to website/src/content/docs/docs/sdk/tools.mdx index fd23e70b3..ef85a0417 100644 --- a/website/src/content/docs/docs/build/tools.mdx +++ b/website/src/content/docs/docs/sdk/tools.mdx @@ -5,9 +5,9 @@ sidebar: order: 3 --- -A tool gives your agent an action: look up an order, read a file, or call a service. `Tool` describes the action's name, parameters, and result. You can pass it directly to an Agent or register it with the [Bub application](/docs/build/app-tools/). +A tool gives your agent an action: look up an order, read a file, or call a service. `Tool` describes the action's name, parameters, and result. You can pass it directly to an Agent or register it with the [Bub application](/docs/app/tools/). -This page covers the shared tool contract and direct SDK use. For installation and a complete model loop, start with [Build your own agent](/docs/build/sdk/). +This page covers the shared tool contract and direct SDK use. For installation and a complete model loop, start with [Build your own agent](/docs/sdk/). @@ -31,7 +31,7 @@ agent = Agent(tools=[add_tool]) The runtime keeps the name `math.add`; model-facing names replace dots with underscores, so the model sees `math_add`. Use `description=` to supply a description explicitly. -Plain `Agent` offers all supplied tools except those marked `exposure="command"`. Deferred loading, the `allowed_tools` state value, and the `state["code_mode"]` switch are [BubAgent policies](/docs/build/app-tools/). To choose tools per run in your own agent, override [`prepare_tools()`](/docs/build/sdk/#customize-the-loop). +Plain `Agent` offers all supplied tools except those marked `exposure="command"`. Deferred loading, the `allowed_tools` state value, and the `state["code_mode"]` switch are [BubAgent policies](/docs/app/tools/). To choose tools per run in your own agent, override [`prepare_tools()`](/docs/sdk/#customize-the-loop). @@ -82,7 +82,7 @@ async def remember(note: str, *, context: ToolContext) -> str: remember_tool = Tool.from_callable(remember, context=True) ``` -This writes a business event to the tape. The default context selector reads conversation messages and tool exchanges; including this event in a model request requires your own selection logic. Use [Tape and context](/docs/concepts/tape-and-context/#context) to decide which saved information the model should read. +This writes a business event to the tape. The default context selector reads conversation messages and tool exchanges; including this event in a model request requires your own selection logic. Use [Tape and context](/docs/concepts/tape/#context) to decide which saved information the model should read. @@ -103,7 +103,7 @@ agent = Agent( ) ``` -Run this agent with the model lifecycle shown in the [SDK guide](/docs/build/sdk/#models). The model can call `run_code` with code such as: +Run this agent with the model lifecycle shown in the [SDK guide](/docs/sdk/#models). The model can call `run_code` with code such as: ```python order = await tools.lookup_order(order_id="A123") @@ -112,7 +112,7 @@ print(order["status"]) Inside `run_code`, tool names use underscores, calls take keyword arguments, and results retain their structured form. Top-level `await` is supported. Calls pass through `ToolCallStarted` and `ToolCallFinished` with `context.code_mode=True`. -`CodeMode` takes the tools you supply, excluding `exposure="command"`. BubAgent groups `auto`, `direct`, and `code` tools through its [application code mode settings](/docs/build/app-tools/). +`CodeMode` takes the tools you supply, excluding `exposure="command"`. BubAgent groups `auto`, `direct`, and `code` tools through its [application code mode settings](/docs/app/tools/). | Option | Behavior | | --- | --- | @@ -165,4 +165,4 @@ asyncio.run(calculate()) It prints `5`. Commands invoke supplied tools and emit tool events plus `CommandFinished`; they bypass the model loop. Prefixes must be non-empty and contain no whitespace. The parser accepts positional arguments followed by `key=value` arguments. A command that does not name a tool falls back to the agent's `bash` tool if one was supplied. -The [Bub application](/docs/build/app-tools/) supplies commands such as `,skill` and `,code_mode`. +The [Bub application](/docs/app/tools/) supplies commands such as `,skill` and `,code_mode`. diff --git a/website/src/content/docs/docs/tutorials/fastapi.mdx b/website/src/content/docs/docs/sdk/web-service.mdx similarity index 86% rename from website/src/content/docs/docs/tutorials/fastapi.mdx rename to website/src/content/docs/docs/sdk/web-service.mdx index 2570e9e3a..f0ceed19f 100644 --- a/website/src/content/docs/docs/tutorials/fastapi.mdx +++ b/website/src/content/docs/docs/sdk/web-service.mdx @@ -7,7 +7,7 @@ sidebar: Give your Agent an HTTP endpoint so another program can send it work. Each request names a session; requests with the same name continue a conversation stored on disk. -This guide follows [Build your own agent](/docs/build/sdk/). It uses `core.Agent` directly, with FastAPI managing the model's lifetime and each request managing its agent stream. +This guide follows [Build your own agent](/docs/sdk/). It uses `core.Agent` directly, with FastAPI managing the model's lifetime and each request managing its agent stream. ## Install @@ -21,7 +21,7 @@ uv add bub fastapi uvicorn This service uses the base SDK dependencies together with FastAPI and Uvicorn. -To add an existing Bub plugin, follow [Reuse application plugins](/docs/build/reuse-plugins/). You can add its tools or selected hooks while keeping the endpoint and request lifecycle below. +To add an existing Bub plugin, follow [Reuse application plugins](/docs/sdk/app-plugins/). You can add its tools or selected hooks while keeping the endpoint and request lifecycle below. ## Write the service @@ -122,8 +122,8 @@ The second answer can refer to the first because both runs read the `customer-42 The example keeps a lock for each session in memory. A service that creates many sessions should remove idle locks. These locks work within one process; multiple workers sharing a tape store need coordination across processes. That coordination must cover the whole run, including model calls and tape writes. -`FileTapeStore` keeps history on one host. To use another backend, implement `TapeStore` or `AsyncTapeStore` and pass it to `Agent(store=...)`; see [Choose where history lives](/docs/concepts/tape-and-context/#storage). Give that store's connections an application lifespan when they need opening and closing. +`FileTapeStore` keeps history on one host. To use another backend, implement `TapeStore` or `AsyncTapeStore` and pass it to `Agent(store=...)`; see [Choose where history lives](/docs/concepts/tape/#storage). Give that store's connections an application lifespan when they need opening and closing. ## Extend the service -Add [tools or CodeMode](/docs/build/tools/) to give the agent new actions. Use [event handlers](/docs/concepts/events/) to observe calls or enforce a decision before execution. Choose what the model sees through [TapeContext](/docs/concepts/tape-and-context/#context). +Add [tools or CodeMode](/docs/sdk/tools/) to give the agent new actions. Use [event handlers](/docs/concepts/events/) to observe calls or enforce a decision before execution. Choose what the model sees through [TapeContext](/docs/concepts/tape/#context). diff --git a/website/src/content/docs/docs/tutorials/index.mdx b/website/src/content/docs/docs/tutorials/index.mdx deleted file mode 100644 index 3cee26dd2..000000000 --- a/website/src/content/docs/docs/tutorials/index.mdx +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Connect Bub to your tools -description: Add an HTTP service, an editor, a model server, or an application integration. -sidebar: - order: 0 ---- - -Choose an integration for the way you use Bub. Each guide names the components it needs and gives you a way to check the result. - -## Build your own agent - -- [Serve an agent with FastAPI](/docs/tutorials/fastapi/): expose `core.Agent` over HTTP and manage conversation storage in your service. - -## Customize the Bub app - -These guides use the app's CLI, configuration, or plugin system. Start with the [app installation](/docs/getting-started/install/) and use the same environment for Bub and its plugins. - -- [Connect MCP servers](/docs/tutorials/mcp/): call tools supplied by an MCP server. -- [Load Agent Plugins](/docs/tutorials/agent-plugins/): load a portable package of skills and MCP servers. -- [Connect an ACP editor](/docs/tutorials/acp-server/): use Bub from an editor such as Zed. -- [Use a local llama.cpp model](/docs/tutorials/local-llama-cpp/): point the app at a local model server. -- [Store conversation history in SQLite](/docs/tutorials/tapestore-sqlalchemy/): replace the app's file store through a plugin. -- [Inspect runs with tapes and traces](/docs/tutorials/observability/): inspect local records or export traces to Phoenix and Logfire. -- [Implement hooks in another language](/docs/tutorials/language-extensions/): try the experimental WebAssembly integration. - -Each integration guide links to the plugin's documentation for package-specific setup. diff --git a/website/src/content/docs/zh-cn/docs/operate/channels/cli.mdx b/website/src/content/docs/zh-cn/docs/app/chat.mdx similarity index 89% rename from website/src/content/docs/zh-cn/docs/operate/channels/cli.mdx rename to website/src/content/docs/zh-cn/docs/app/chat.mdx index ed33545e4..8f487b2d2 100644 --- a/website/src/content/docs/zh-cn/docs/operate/channels/cli.mdx +++ b/website/src/content/docs/zh-cn/docs/app/chat.mdx @@ -3,7 +3,7 @@ title: 使用终端 description: 用 bub chat 继续对话,或用 bub run 从 shell 执行一个任务。 --- -Bub 应用提供两种终端用法:`bub chat` 用于交互式对话,`bub run` 用于执行一条消息。先[配置模型](/zh-cn/docs/operate/configure/)。 +Bub 应用提供两种终端用法:`bub chat` 用于交互式对话,`bub run` 用于执行一条消息。先[配置模型](/zh-cn/docs/app/configure/)。 ## 继续一段对话 @@ -41,4 +41,4 @@ bub run ",fs.read path=README.md" 在交互式对话中也可以输入这些命令。Ctrl-X 切换 shell 模式,为输入自动添加命令前缀。 -CLI 使用前台终端,因此网关默认不启动它。网关的渠道选择和消息合并规则见[渠道](/zh-cn/docs/operate/channels/),全部参数见 [CLI 参考](/zh-cn/docs/reference/cli/)。 +CLI 使用前台终端,因此网关默认不启动它。网关的渠道选择和消息合并规则见[渠道](/zh-cn/docs/app/gateway/),全部参数见 [CLI 参考](/zh-cn/docs/reference/cli/)。 diff --git a/website/src/content/docs/zh-cn/docs/operate/configure.mdx b/website/src/content/docs/zh-cn/docs/app/configure.mdx similarity index 95% rename from website/src/content/docs/zh-cn/docs/operate/configure.mdx rename to website/src/content/docs/zh-cn/docs/app/configure.mdx index 5b7e6e5e3..b0d3ad693 100644 --- a/website/src/content/docs/zh-cn/docs/operate/configure.mdx +++ b/website/src/content/docs/zh-cn/docs/app/configure.mdx @@ -5,7 +5,7 @@ sidebar: order: 1 --- -选择 Bub 应用调用的模型和接入的渠道。这些配置属于应用;直接构造 `core.Agent` 时,在 Python 中传入模型与组件,参见[构建自己的 Agent](/zh-cn/docs/build/sdk/)。 +选择 Bub 应用调用的模型和接入的渠道。这些配置属于应用;直接构造 `core.Agent` 时,在 Python 中传入模型与组件,参见[构建自己的 Agent](/zh-cn/docs/sdk/)。 Bub 自己的配置按以下优先级(从高到低)读取: @@ -81,7 +81,7 @@ BUB_TELEGRAM_ALLOW_CHATS=-1001234567890 BUB_TELEGRAM_PROXY=http://127.0.0.1:7890 # 可选 ``` -这些变量驱动的消息处理与访问控制行为见 [Telegram](/zh-cn/docs/operate/channels/telegram/)。 +这些变量驱动的消息处理与访问控制行为见 [Telegram](/zh-cn/docs/app/telegram/)。 ## 5. 验证 @@ -95,6 +95,6 @@ bub run "say hi" ## 后续阅读 -- [渠道](/zh-cn/docs/operate/channels/):选择 Bub 监听的接入面。 +- [渠道](/zh-cn/docs/app/gateway/):选择 Bub 监听的接入面。 - [设置参考](/zh-cn/docs/reference/settings/):完整环境变量表。 -- [部署](/zh-cn/docs/operate/deploy/):把这份配置打入容器。 +- [部署](/zh-cn/docs/app/deploy/):把这份配置打入容器。 diff --git a/website/src/content/docs/zh-cn/docs/operate/deploy.mdx b/website/src/content/docs/zh-cn/docs/app/deploy.mdx similarity index 94% rename from website/src/content/docs/zh-cn/docs/operate/deploy.mdx rename to website/src/content/docs/zh-cn/docs/app/deploy.mdx index fa3664a38..fccc0e994 100644 --- a/website/src/content/docs/zh-cn/docs/operate/deploy.mdx +++ b/website/src/content/docs/zh-cn/docs/app/deploy.mdx @@ -12,8 +12,8 @@ sidebar: ## 开始之前 - 已安装 Docker(或兼容运行时)与 `docker compose`。 -- `.env` 中至少包含 `BUB_MODEL` 以及 `BUB_API_KEY` 或某个提供商专用 key:参见 [配置](/zh-cn/docs/operate/configure/)。 -- Telegram 部署还需要 `.env` 中包含 `BUB_TELEGRAM_TOKEN` 与一份白名单:参见 [Telegram](/zh-cn/docs/operate/channels/telegram/)。 +- `.env` 中至少包含 `BUB_MODEL` 以及 `BUB_API_KEY` 或某个提供商专用 key:参见 [配置](/zh-cn/docs/app/configure/)。 +- Telegram 部署还需要 `.env` 中包含 `BUB_TELEGRAM_TOKEN` 与一份白名单:参见 [Telegram](/zh-cn/docs/app/telegram/)。 ## 1. 准备 env 文件 @@ -139,6 +139,6 @@ docker compose exec app bub update specific-plugin # 升级指定插件 ## 后续阅读 -- [配置](/zh-cn/docs/operate/configure/):环境变量优先级与提供商设置。 -- [Telegram](/zh-cn/docs/operate/channels/telegram/):bot token 与白名单行为。 +- [配置](/zh-cn/docs/app/configure/):环境变量优先级与提供商设置。 +- [Telegram](/zh-cn/docs/app/telegram/):bot token 与白名单行为。 - [CLI 参考](/zh-cn/docs/reference/cli/):`bub gateway`、`bub install`、`bub update` 的参数。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/first-turn.mdx b/website/src/content/docs/zh-cn/docs/app/first-session.mdx similarity index 72% rename from website/src/content/docs/zh-cn/docs/getting-started/first-turn.mdx rename to website/src/content/docs/zh-cn/docs/app/first-session.mdx index 3d5416953..bf9154c62 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/first-turn.mdx +++ b/website/src/content/docs/zh-cn/docs/app/first-session.mdx @@ -7,7 +7,7 @@ sidebar: 从现成的 Bub 应用开始,再让它适应你的工作。这篇教程会准备工作区和指令,检查命令是否可用,然后与模型完成一次对话。 -先按[安装 Bub](/zh-cn/docs/getting-started/install/)完成应用安装,再按[配置 Bub](/zh-cn/docs/operate/configure/)选择模型。对话步骤需要对应供应商的凭据;检查命令时不需要。 +先按[安装 Bub](/zh-cn/docs/getting-started/install/)完成应用安装,再按[配置 Bub](/zh-cn/docs/app/configure/)选择模型。对话步骤需要对应供应商的凭据;检查命令时不需要。 ## 准备工作区 @@ -54,10 +54,10 @@ Bub 把消息和工作区指令交给 agent,记录会话历史,并打印回 bub chat ``` -如果命令可用而对话失败,按[配置 Bub](/zh-cn/docs/operate/configure/)检查模型名称和凭据。如果某个插件未生效,运行 `bub hooks` 查看应用发现了哪些 hook 实现。 +如果命令可用而对话失败,按[配置 Bub](/zh-cn/docs/app/configure/)检查模型名称和凭据。如果某个插件未生效,运行 `bub hooks` 查看应用发现了哪些 hook 实现。 ## 按你的需要定制 -- 添加[项目 skill](/zh-cn/docs/getting-started/first-skill/),让 Bub 遵循常用流程。 -- 添加[应用工具](/zh-cn/docs/build/app-tools/),让 Bub 调用自己的代码。 -- 编写[插件](/zh-cn/docs/getting-started/first-plugin/),改变应用行为。 +- 添加[项目 skill](/zh-cn/docs/app/skills/),让 Bub 遵循常用流程。 +- 添加[应用工具](/zh-cn/docs/app/tools/),让 Bub 调用自己的代码。 +- 编写[插件](/zh-cn/docs/app/plugins/),改变应用行为。 diff --git a/website/src/content/docs/zh-cn/docs/operate/channels/index.mdx b/website/src/content/docs/zh-cn/docs/app/gateway.mdx similarity index 87% rename from website/src/content/docs/zh-cn/docs/operate/channels/index.mdx rename to website/src/content/docs/zh-cn/docs/app/gateway.mdx index a34ff0b65..12f47aa23 100644 --- a/website/src/content/docs/zh-cn/docs/operate/channels/index.mdx +++ b/website/src/content/docs/zh-cn/docs/app/gateway.mdx @@ -9,7 +9,7 @@ sidebar: ## 开始之前 -- 你已运行 `bub onboard` 或直接编辑了 `~/.bub/config.yml`。参见 [配置](/zh-cn/docs/operate/configure/)。 +- 你已运行 `bub onboard` 或直接编辑了 `~/.bub/config.yml`。参见 [配置](/zh-cn/docs/app/configure/)。 - 对于每个非 CLI 渠道,相应凭据已设置(如 `BUB_TELEGRAM_TOKEN`)。 ## 1. 选择启用哪些 channel @@ -78,13 +78,13 @@ Bub 用 `session_id` 索引会话状态。默认规则是 `:` | Channel | 默认启用? | 防抖? | 页面 | |---|---|---|---| -| `cli` | 仅在 `bub chat` 中 | 否 | [CLI](/zh-cn/docs/operate/channels/cli/) | -| `telegram` | 是(当 `BUB_TELEGRAM_TOKEN` 已设置时) | 是 | [Telegram](/zh-cn/docs/operate/channels/telegram/) | +| `cli` | 仅在 `bub chat` 中 | 否 | [CLI](/zh-cn/docs/app/chat/) | +| `telegram` | 是(当 `BUB_TELEGRAM_TOKEN` 已设置时) | 是 | [Telegram](/zh-cn/docs/app/telegram/) | -插件可通过 `provide_channels` hook 注册更多渠道。其他渠道可以通过[插件](/zh-cn/docs/build/plugins/)接入。 +插件可通过 `provide_channels` hook 注册更多渠道。其他渠道可以通过[插件](/zh-cn/docs/app/plugins/)接入。 ## 后续阅读 -- [CLI](/zh-cn/docs/operate/channels/cli/):REPL 与单次轮次。 -- [Telegram](/zh-cn/docs/operate/channels/telegram/):token、白名单、群聊行为。 +- [CLI](/zh-cn/docs/app/chat/):REPL 与单次轮次。 +- [Telegram](/zh-cn/docs/app/telegram/):token、白名单、群聊行为。 - [设置参考](/zh-cn/docs/reference/settings/):全部渠道变量。 diff --git a/website/src/content/docs/zh-cn/docs/build/hooks.mdx b/website/src/content/docs/zh-cn/docs/app/hooks.mdx similarity index 89% rename from website/src/content/docs/zh-cn/docs/build/hooks.mdx rename to website/src/content/docs/zh-cn/docs/app/hooks.mdx index f827357bd..a2fedb517 100644 --- a/website/src/content/docs/zh-cn/docs/build/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/app/hooks.mdx @@ -7,7 +7,7 @@ sidebar: 在你想改变的位置选择 hook,其余部分继续使用 Bub 应用的实现。提示词 hook 改变输入,工具 decision 控制一次调用,存储 provider 决定会话历史保存在哪里。 -把这些例子放进[插件](/zh-cn/docs/build/plugins/)。模块级函数可以使用模块入口点;类需要注册实例,或使用接收 framework 的工厂。 +把这些例子放进[插件](/zh-cn/docs/app/plugins/)。模块级函数可以使用模块入口点;类需要注册实例,或使用接收 framework 的工厂。 [Hooks 参考](/zh-cn/docs/reference/hooks/)定义了签名、优先级和生命周期规则。 @@ -94,7 +94,7 @@ def after_tool_call(call, result, state): 这些 hooks 观察已经完成的调用,包括普通异常导致的失败,但不报告取消。需要逐块处理输出时,在宿主程序或渠道中使用流接口,并遵守[关闭规则](/zh-cn/docs/reference/types/#streaming)。 -如果要替换整个模型阶段,实现 `run_model` 或 `run_model_stream` 中的一个。[第一个插件](/zh-cn/docs/getting-started/first-plugin/)演示了文本替换。替换实现负责自己的执行和记录行为;只有使用内置 agent 时,才会运行它的拦截 hooks。 +如果要替换整个模型阶段,实现 `run_model` 或 `run_model_stream` 中的一个。[第一个插件](/zh-cn/docs/app/plugins/)演示了文本替换。替换实现负责自己的执行和记录行为;只有使用内置 agent 时,才会运行它的拦截 hooks。 ## 跨轮次保留应用状态 @@ -122,7 +122,7 @@ turn_counter = TurnCounter() 这个内存示例统计模型阶段退出的次数,包括失败。进程退出后计数会丢失,并且它假定同一会话一次只处理一轮。生产环境应使用持久化存储,并选择合适的并发策略。 -`save_state` 位于模型阶段的 `finally` 块中,前提是会话解析、状态加载和提示词准备已经成功。这些准备阶段发生错误时,会跳过保存 hook。会话历史另有 [Tape 生命周期](/zh-cn/docs/concepts/tape-and-context/#state)。 +`save_state` 位于模型阶段的 `finally` 块中,前提是会话解析、状态加载和提示词准备已经成功。这些准备阶段发生错误时,会跳过保存 hook。会话历史另有 [Tape 生命周期](/zh-cn/docs/concepts/tape/#state)。 @@ -143,7 +143,7 @@ def provide_tape_store(): return FileTapeStore(Path("./application-tapes")) ``` -这个例子选择当前目录下的普通 JSONL 文件存储。Bub 应用的内置存储还提供应用所需的搜索行为;按任务选择存储实现。需要数据库存储时,参见 [SQLAlchemy 集成](/zh-cn/docs/tutorials/tapestore-sqlalchemy/)。 +这个例子选择当前目录下的普通 JSONL 文件存储。Bub 应用的内置存储还提供应用所需的搜索行为;按任务选择存储实现。需要数据库存储时,参见 [SQLAlchemy 集成](/zh-cn/docs/integrations/storage/)。 Framework 在进入 `framework.running()` 时解析一次 provider。Provider 也可以 yield 一个 store,并在 `finally` 中释放资源。[参考文档](/zh-cn/docs/reference/hooks/#runtime-resources)说明了这个生命周期,以及对应的 `provide_lifespan` hook。 @@ -162,7 +162,7 @@ def provide_tape_sidecar(): return ArtifactSidecar() ``` -[公共 Tape 说明](/zh-cn/docs/concepts/tape-and-context/)介绍了 sidecar 的命名和合并行为。 +[公共 Tape 说明](/zh-cn/docs/concepts/tape/)介绍了 sidecar 的命名和合并行为。 ## 添加应用命令 diff --git a/website/src/content/docs/zh-cn/docs/app/index.mdx b/website/src/content/docs/zh-cn/docs/app/index.mdx new file mode 100644 index 000000000..1a0f2c044 --- /dev/null +++ b/website/src/content/docs/zh-cn/docs/app/index.mdx @@ -0,0 +1,42 @@ +--- +title: 使用 Bub 应用 +description: 运行 Bub 应用、按你的工作需要配置它,并决定要添加哪些能力。 +sidebar: + order: 0 +--- + +从能运行的 Bub 应用开始,添加工作中需要的行为。内置 `BubAgent` 扩展了 `core.Agent`;Framework 负责应用编排、插件 hooks 和渠道路由。 + +下面的指南沿用 Bub 应用的配置和扩展接口。如果你希望在 Python 程序中直接传入模型和工具,请从[构建你的 Bub Agent](/zh-cn/docs/sdk/)开始。 + +## 运行应用 + +完成[应用安装](/zh-cn/docs/getting-started/install/#app-install),[运行第一轮对话](/zh-cn/docs/app/first-session/),再按需要[配置模型和工作区](/zh-cn/docs/app/configure/)。 + +日常使用中,选择 Bub 以什么方式触达你: + +- [使用终端](/zh-cn/docs/app/chat/):执行一个任务,或继续交互式对话。 +- [接入 Telegram](/zh-cn/docs/app/telegram/):设置机器人,选择允许使用它的人。 +- [运行网关](/zh-cn/docs/app/gateway/):在一个进程里监听所有已启用的渠道。 +- [部署应用](/zh-cn/docs/app/deploy/):构建容器,在重启后保留数据。 + +## 选择要添加的能力 + +| 你要完成的工作 | 从这里开始 | +| --- | --- | +| 让模型调用一个函数 | [Bub 应用中的工具](/zh-cn/docs/app/tools/) | +| 给模型提供可复用的操作说明 | [Skills](/zh-cn/docs/app/skills/) | +| 打包集成或替换应用 hook | [插件](/zh-cn/docs/app/plugins/)与 [Hooks](/zh-cn/docs/app/hooks/) | +| 通过 CLI、Telegram 或其他适配器接收消息 | [渠道](/zh-cn/docs/app/gateway/) | +| 打包自己的发行版 | [打包 Bub 应用](/zh-cn/docs/app/package/) | +| 持续运行 Bub 服务 | [部署](/zh-cn/docs/app/deploy/) | + +工具、会话历史和 Agent 事件遵循[共同契约](/zh-cn/docs/concepts/)。应用还提供工具加载、代码模式选择等默认策略。[Bub 应用中的工具](/zh-cn/docs/app/tools/)说明这些策略;[一轮对话](/zh-cn/docs/concepts/turn/)说明 Framework 如何连接消息接收与响应发送。 + +## 复用已有集成 + +- 在 [Bub Hub](https://hub.bub.build) 浏览插件、skills 与发行版。 +- 按照[集成指南](/zh-cn/docs/integrations/)接入具体服务和协议。 +- 在[应用参考](/zh-cn/docs/reference/#application)中查阅签名与默认值。 + +如果你的服务直接管理 `core.Agent`,请沿[用 FastAPI 提供 Agent 服务](/zh-cn/docs/sdk/web-service/)继续。该路线在 Python 中管理模型和存储。 diff --git a/website/src/content/docs/zh-cn/docs/tutorials/language-extensions.mdx b/website/src/content/docs/zh-cn/docs/app/language-extensions.mdx similarity index 97% rename from website/src/content/docs/zh-cn/docs/tutorials/language-extensions.mdx rename to website/src/content/docs/zh-cn/docs/app/language-extensions.mdx index 39a5fade1..959bae5cc 100644 --- a/website/src/content/docs/zh-cn/docs/tutorials/language-extensions.mdx +++ b/website/src/content/docs/zh-cn/docs/app/language-extensions.mdx @@ -2,6 +2,7 @@ title: 用其他语言实现 hooks description: 通过实验性的 Extism 插件,用 WebAssembly 实现部分应用 hooks。 sidebar: + badge: { text: '实验性', variant: 'caution' } order: 5 --- @@ -144,6 +145,6 @@ Go 模块构造提示词,Rust 模块按示例格式返回响应。这个示例 ## 下一步 -- [构建插件](/zh-cn/docs/build/plugins/):为 Bub 应用打包 Python 集成。 +- [构建插件](/zh-cn/docs/app/plugins/):为 Bub 应用打包 Python 集成。 - [`bub-extism` README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism):阅读 package 约定及支持的 hooks。 - [Extism 概览](https://extism.org/docs/overview):了解 Extism 如何在不同语言中运行 wasm 模块。 diff --git a/website/src/content/docs/zh-cn/docs/build/distribution.mdx b/website/src/content/docs/zh-cn/docs/app/package.mdx similarity index 90% rename from website/src/content/docs/zh-cn/docs/build/distribution.mdx rename to website/src/content/docs/zh-cn/docs/app/package.mdx index 499d54b07..a64fa57e6 100644 --- a/website/src/content/docs/zh-cn/docs/build/distribution.mdx +++ b/website/src/content/docs/zh-cn/docs/app/package.mdx @@ -7,7 +7,7 @@ sidebar: 如果产品需要 Bub 的配置、插件和消息路由,可以在 Bub 应用上继续构建。你可以把 `BubFramework` 放进现有进程,也可以发布一个启动它的命令。 -这条路径使用[安装 Bub](/zh-cn/docs/getting-started/install/)中说明的应用依赖。如果希望直接组合 Agent 和各个组件,请从[构建自己的 Agent](/zh-cn/docs/build/sdk/)开始。 +这条路径使用[安装 Bub](/zh-cn/docs/getting-started/install/)中说明的应用依赖。如果希望直接组合 Agent 和各个组件,请从[构建自己的 Agent](/zh-cn/docs/sdk/)开始。 @@ -49,7 +49,7 @@ asyncio.run(main()) ## 在自己的服务中复用插件 -宿主可以使用插件提供的工具、存储或 hooks,同时保留自己的 API 和流处理方式。按[在 Agent 中复用插件](/zh-cn/docs/build/reuse-plugins/)中的步骤,把这些能力接到 `core.Agent` 上。 +宿主可以使用插件提供的工具、存储或 hooks,同时保留自己的 API 和流处理方式。按[在 Agent 中复用插件](/zh-cn/docs/sdk/app-plugins/)中的步骤,把这些能力接到 `core.Agent` 上。 `BubAgent` 还包含应用的提示词和渠道发送约定。需要这些应用行为时,可以选择它,并在宿主中接好对应的输出处理。 @@ -87,6 +87,6 @@ channels = "my_bub.channels:ChannelPlugin" packages = ["src/my_bub", "src/skills"] ``` -每个目标都遵循[插件注册规则](/zh-cn/docs/build/plugins/)。任务指令按 [skills 包目录结构](/zh-cn/docs/build/skills/#ship-skills-with-your-package)加入发行包。 +每个目标都遵循[插件注册规则](/zh-cn/docs/app/plugins/)。任务指令按 [skills 包目录结构](/zh-cn/docs/app/skills/#ship-skills-with-your-package)加入发行包。 发布前,把构建好的 wheel 安装到独立环境,运行命令、核对插件报告、加载打包的 skill,再完成一次有代表性的对话。对于宿主应用,还应检查流或后台工具运行期间的关闭行为。 diff --git a/website/src/content/docs/zh-cn/docs/app/plugins.mdx b/website/src/content/docs/zh-cn/docs/app/plugins.mdx new file mode 100644 index 000000000..14d0798ea --- /dev/null +++ b/website/src/content/docs/zh-cn/docs/app/plugins.mdx @@ -0,0 +1,162 @@ +--- +title: 插件 +description: 编写插件、选择入口点形式,并安装到应用里。 +sidebar: + order: 0 +--- + +插件可以改变 Bub 应用处理任务的方式。本页先带你写一个回声插件,无需模型凭据就能检查插件发现和轮次处理,再说明入口点的几种形式、工具注册和分享方式。 + +Python 包在 `bub` 入口点组中声明目标,Bub 导入这个目标,并注册其中的 hook 实现。你也可以在自己的服务中复用现有插件,继续使用宿主自己的调用入口;参见[借用应用运行时](/zh-cn/docs/sdk/app-plugins/)。 + +## 创建包 + +按[安装 Bub](/zh-cn/docs/getting-started/install/)中的说明准备 Python 3.12+ 和 uv。这篇教程会创建一个同时包含 Bub 0.6.0 和插件的项目环境。 + +创建以下包目录: + +```text +bub-echo-plugin/ +├── pyproject.toml +└── src/ + └── bub_echo_plugin/ + ├── __init__.py + └── plugin.py +``` + +将 `__init__.py` 留空。在 `pyproject.toml` 中写入: + +```toml +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "bub-echo-plugin" +version = "0.1.0" +requires-python = ">=3.12" +dependencies = ["bub[app]>=0.6.0"] + +[project.entry-points."bub"] +echo = "bub_echo_plugin.plugin:echo_plugin" + +[tool.hatch.build.targets.wheel] +packages = ["src/bub_echo_plugin"] +``` + +`bub` 入口点组告诉应用到哪里寻找插件。 + +## 实现回复 + +在 `src/bub_echo_plugin/plugin.py` 中写入: + +```python +from bub import hookimpl + + +class EchoPlugin: + @hookimpl + def run_model(self, prompt, session_id, state): + return f"[echo:{session_id}] {prompt}" + + +echo_plugin = EchoPlugin() +``` + +应用仍会构建收到的提示词。你的 `run_model` 实现提供回复,替代内置 agent 的执行。 + +## 安装并试运行 + +在插件目录安装项目和依赖: + +```bash +uv sync +uv run bub hooks +``` + +`uv sync` 会以可编辑模式安装这个包。在同一个项目中通过 `uv run` 运行 Bub,它就能发现插件。如果安装到已有且已激活的应用环境,改用 `uv pip install -e .`,然后在该环境中运行 `bub hooks`。 + +报告应在 `run_model` 下列出 `echo`。内置 agent 则出现在 `run_model_stream` 下。 + +```bash +uv run bub run "hello from my plugin" +``` + +没有其他插件覆盖模型阶段时,回复包含: + +```text +[echo:cli:local] hello from my plugin +``` + +如果没有看到 `echo`,检查安装插件的环境是否就是运行 `bub` 的环境、入口点组名,以及启动日志中的导入错误。修改包元数据后,需要重新安装包。如果已经列出却返回其他回复,检查其他模型 hooks 及其优先级。 + +Bub 先注册内置 hooks,再注册入口点插件。同等优先级通常按注册顺序的逆序调用;`tryfirst` 和 `trylast` 会影响顺序。[调用规则](/zh-cn/docs/reference/hooks/#iteration-order)说明了哪个实现先运行。 + +## 选择入口点形式 + +键 `myplugin` 是诊断信息中的插件名称,值是要导入的 Python 对象。选择一种目标形式: + +| 目标 | 注册方式 | 适用情况 | +| --- | --- | --- | +| `bub_myplugin.plugin` | 注册模块 | hooks 是模块级函数 | +| `bub_myplugin.plugin:plugin` | 注册实例 | hooks 需要共享对象状态 | +| `bub_myplugin.plugin:MyPlugin` | 将 `BubFramework` 传给类或工厂,注册返回值 | hooks 需要访问 framework | + +模块形式在模块顶层定义 hooks: + +```python +from bub import hookimpl + + +@hookimpl +def build_prompt(message, session_id, state): + return "Describe this workspace." +``` + +实例形式在目标模块中创建对象: + +```python +from bub import hookimpl + + +class MyPlugin: + @hookimpl + def build_prompt(self, message, session_id, state): + return "Describe this workspace." + + +plugin = MyPlugin() +``` + +所有可调用目标都会收到 framework,类也一样。构造函数或工厂需要接收这个参数: + +```python +from bub import BubFramework, hookimpl + + +class MyPlugin: + def __init__(self, framework: BubFramework) -> None: + self.framework = framework + + @hookimpl + def build_prompt(self, message, session_id, state): + return f"Describe the workspace at {self.framework.workspace}." +``` + +这些例子会替换提示词。请在[改变应用行为](/zh-cn/docs/app/hooks/)中选择真正适合任务的 hook。 + +## 导入时注册工具 + +如果包内定义了 `@tool` 函数,在入口点模块中导入工具模块,确保 agent 创建前已完成注册: + +```python +from . import tools # noqa: F401 +``` + +应用创建 `BubAgent` 时会获取已注册工具的快照。[添加应用工具](/zh-cn/docs/app/tools/)会说明注册、延迟加载和代码模式。 + +## 分享插件 + +发布前,构建 wheel 并安装到独立环境,确认入口点能加载,并用一个实际任务检查 hook。如果要同时发布 skills 或多个插件,参见[打包 Bub 应用](/zh-cn/docs/app/package/)。 + +可移植的 Agent Plugins 使用另一种格式,包括 `plugin.json`、skills 和 MCP servers。[Agent Plugins 集成](/zh-cn/docs/integrations/agent-plugins/)通过一个 Python 插件加载这些包。 diff --git a/website/src/content/docs/zh-cn/docs/build/skills.mdx b/website/src/content/docs/zh-cn/docs/app/skills.mdx similarity index 62% rename from website/src/content/docs/zh-cn/docs/build/skills.mdx rename to website/src/content/docs/zh-cn/docs/app/skills.mdx index cdc6dac0b..b766cfede 100644 --- a/website/src/content/docs/zh-cn/docs/build/skills.mdx +++ b/website/src/content/docs/zh-cn/docs/app/skills.mdx @@ -1,15 +1,56 @@ --- -title: 为应用打包 skills -description: 分享任务指令和配套文件,让 Bub 能够发现并使用它们。 +title: Skills +description: 编写可复用的流程,确认应用能加载它,并打包给其他用户使用。 sidebar: - order: 4 + order: 0 --- -skill 为 Bub 应用提供完成任务的流程。项目专用的 skills 可以留在工作区,也可以随 Python 包分发,让其他用户安装使用。 +skill 为 Bub 应用提供完成任务的流程。本页先带你写一个项目 skill,再说明文件字段、发现顺序和打包方式。项目专用的 skills 可以留在工作区,也可以随 Python 包分发,让其他用户安装使用。 -第一次编写本地 skill,请从[添加项目 skill](/zh-cn/docs/getting-started/first-skill/)开始。本页说明发现机制和打包方式。自动发现 skills 是应用提供的行为;普通 `core.Agent` 使用你显式传入的指令。 +自动发现 skills 是应用提供的行为;普通 `core.Agent` 使用你显式传入的指令。 -## 编写 Bub 能加载的 skill +## 编写指令 + +创建 skill 目录: + +```bash +mkdir -p .agents/skills/repo-map +``` + +创建 `.agents/skills/repo-map/SKILL.md`: + +```markdown +--- +name: repo-map +description: 修改代码前,先阅读本地仓库结构。 +--- + +1. 运行 `rg --files`,查看仓库结构。 +2. 阅读能够说明本次修改的文件。 +3. 编辑代码前,说明会涉及哪些模块。 +``` + +`name` 必须与目录名一致,使用连字符分隔的小写单词。description 帮助模型判断何时使用这个 skill。 + +## 检查能否加载 + +```bash +bub run ",skill name=repo-map" +``` + +命令会打印 skill 的指令,不调用模型。如果返回 `(no such skill)`,检查文件路径和 YAML frontmatter。 + +## 在对话中使用 + +配置好模型后,运行: + +```bash +bub run '使用 $repo-map skill,然后列出你找到的顶层模块。' +``` + +`$repo-map` 会把该 skill 的指令加入本轮系统提示词。模型随后使用应用中可用的工具执行这些步骤。这个例子需要在执行环境中安装 `rg`。 + +## skill 文件字段 每个 skill 目录包含带 YAML frontmatter 的 `SKILL.md`: @@ -84,4 +125,4 @@ bub run ",skill name=my-skill" 如果返回 `(no such skill)`,检查目录名、frontmatter 和 wheel 内容。如果打印了意外的版本,检查项目或用户目录下是否有同名 skill。 -要一起分发 CLI 和插件,参见[打包 Bub 应用](/zh-cn/docs/build/distribution/)。 +要一起分发 CLI 和插件,参见[打包 Bub 应用](/zh-cn/docs/app/package/)。 diff --git a/website/src/content/docs/zh-cn/docs/operate/channels/telegram.mdx b/website/src/content/docs/zh-cn/docs/app/telegram.mdx similarity index 92% rename from website/src/content/docs/zh-cn/docs/operate/channels/telegram.mdx rename to website/src/content/docs/zh-cn/docs/app/telegram.mdx index 7276704b4..8d7ebfaf9 100644 --- a/website/src/content/docs/zh-cn/docs/operate/channels/telegram.mdx +++ b/website/src/content/docs/zh-cn/docs/app/telegram.mdx @@ -11,7 +11,7 @@ Telegram 渠道注册名为 `telegram`。它通过 `python-telegram-bot` 使用 - 已通过 [@BotFather](https://t.me/BotFather) 创建机器人并拿到 token。 - 群聊使用时,**已在 BotFather 中关闭 Privacy Mode**。否则机器人只能看到命令,入站过滤器无法判断是否要回复。 -- Bub 已配置可用的模型:参见 [配置](/zh-cn/docs/operate/configure/)。 +- Bub 已配置可用的模型:参见 [配置](/zh-cn/docs/app/configure/)。 ## 1. 设置 token @@ -75,7 +75,7 @@ Telegram 渠道使用 `telegram:` 作为会话 ID。即便同一用户 ## 6. 防抖 -Telegram 渠道设置 `needs_debounce=True`。入站消息按会话用 `BUB_DEBOUNCE_SECONDS`、`BUB_MAX_WAIT_SECONDS` 与 `BUB_ACTIVE_TIME_WINDOW` 进行批处理。具体值见 [渠道总览](/zh-cn/docs/operate/channels/)。 +Telegram 渠道设置 `needs_debounce=True`。入站消息按会话用 `BUB_DEBOUNCE_SECONDS`、`BUB_MAX_WAIT_SECONDS` 与 `BUB_ACTIVE_TIME_WINDOW` 进行批处理。具体值见 [渠道总览](/zh-cn/docs/app/gateway/)。 :::tip 逗号命令(`,help`、`,fs.read ...`)始终绕过防抖。无论是否有待处理批次,它们一到达就被分发。 @@ -89,6 +89,6 @@ Telegram 渠道设置 `needs_debounce=True`。入站消息按会话用 `BUB_DEBO ## 后续阅读 -- [渠道总览](/zh-cn/docs/operate/channels/):防抖、`enabled_channels`、会话 ID。 -- [部署](/zh-cn/docs/operate/deploy/):把机器人打入容器并持久化状态。 +- [渠道总览](/zh-cn/docs/app/gateway/):防抖、`enabled_channels`、会话 ID。 +- [部署](/zh-cn/docs/app/deploy/):把机器人打入容器并持久化状态。 - [设置参考](/zh-cn/docs/reference/settings/):全部 `BUB_TELEGRAM_*` 变量。 diff --git a/website/src/content/docs/zh-cn/docs/build/app-tools.mdx b/website/src/content/docs/zh-cn/docs/app/tools.mdx similarity index 81% rename from website/src/content/docs/zh-cn/docs/build/app-tools.mdx rename to website/src/content/docs/zh-cn/docs/app/tools.mdx index d2fe4f121..df78a0ebe 100644 --- a/website/src/content/docs/zh-cn/docs/build/app-tools.mdx +++ b/website/src/content/docs/zh-cn/docs/app/tools.mdx @@ -5,7 +5,7 @@ sidebar: order: 3 --- -给 Bub 应用添加工作中需要执行的动作。[公共工具指南](/zh-cn/docs/build/tools/)说明函数参数、结果和工具上下文。本页介绍应用注册,以及 `BubAgent` 执行的工具策略。 +给 Bub 应用添加工作中需要执行的动作。[公共工具指南](/zh-cn/docs/sdk/tools/)说明函数参数、结果和工具上下文。本页介绍应用注册,以及 `BubAgent` 执行的工具策略。 ## 注册工具 @@ -21,7 +21,7 @@ def get_order(order_id: str) -> dict[str, str]: return {"id": order_id, "status": "shipped"} ``` -装饰器只创建 `Tool`。在[插件](/zh-cn/docs/build/plugins/)中通过 `provide_tools` hook 提供它: +装饰器只创建 `Tool`。在[插件](/zh-cn/docs/app/plugins/)中通过 `provide_tools` hook 提供它: ```python from bub import hookimpl @@ -44,7 +44,7 @@ class OrdersPlugin: 调用 `BubAgent.run_stream` 时,传入 `allowed_tools` 限制本轮模型可用的工具。它接受运行时名称或模型名称。`None` 允许全部实例工具,空集合不允许任何工具。未知名称会抛出 `ValueError`。 -这个筛选适用于模型和代码模式中的工具。逗号命令直接使用 agent 实例的工具。如果规则也需要约束命令执行,使用[工具调用 decision](/zh-cn/docs/build/hooks/#control-calls)。 +这个筛选适用于模型和代码模式中的工具。逗号命令直接使用 agent 实例的工具。如果规则也需要约束命令执行,使用[工具调用 decision](/zh-cn/docs/app/hooks/#control-calls)。 @@ -89,9 +89,9 @@ def get_order(order_id: str) -> dict[str, str]: 如果排除了 `run_code`,本轮按关闭代码模式时的规则选择工具。应用把 Python 工具声明文件写入 `/codemode/`,并把路径加入系统提示词。`await tools.orders_get(order_id="123")` 这样的 Python 调用使用已选工具,并经过应用的拦截 hooks。 -应用把 CodeMode 接到会话的执行环境和模型设置上。代码可以使用 [CodeMode](/zh-cn/docs/build/tools/#code-mode) 中介绍的 Republic bridge 和媒体附加工具。通过 bridge 使用模型时,凭据保留在宿主侧;默认的本地执行环境仍在这个宿主上运行代码。 +应用把 CodeMode 接到会话的执行环境和模型设置上。代码可以使用 [CodeMode](/zh-cn/docs/sdk/tools/#code-mode) 中介绍的 Republic bridge 和媒体附加工具。通过 bridge 使用模型时,凭据保留在宿主侧;默认的本地执行环境仍在这个宿主上运行代码。 -`BubAgent` 读取 `state["code_mode"]` 和 `deferred` 标记,执行这些筛选规则。使用普通 `core.Agent` 时,需要[显式组合 CodeMode](/zh-cn/docs/build/tools/#code-mode);如需延迟加载,也由你提供相应策略。 +`BubAgent` 读取 `state["code_mode"]` 和 `deferred` 标记,执行这些筛选规则。使用普通 `core.Agent` 时,需要[显式组合 CodeMode](/zh-cn/docs/sdk/tools/#code-mode);如需延迟加载,也由你提供相应策略。 @@ -99,12 +99,12 @@ def get_order(order_id: str) -> dict[str, str]: 内置 `bash`、`bash.output`、`bash.kill`、`fs.*` 和 `run_code` 使用会话的 `Environment`。默认是运行在宿主上的 `LocalEnvironment`。 -插件通过 `provide_environment(session_id, workspace)` hook 提供其他环境。Framework 为每个会话缓存一个环境,并在退出 `framework.running()` 时关闭。[公共工具指南](/zh-cn/docs/build/tools/#run-tools-in-an-environment)说明环境接口。 +插件通过 `provide_environment(session_id, workspace)` hook 提供其他环境。Framework 为每个会话缓存一个环境,并在退出 `framework.running()` 时关闭。[公共工具指南](/zh-cn/docs/sdk/tools/#run-tools-in-an-environment)说明环境接口。 自定义应用工具可以用 `bub.builtin.environment` 中的 `environment_from_state(context.state)` 取得环境。Python 工具本身在宿主侧运行,需要时再显式调用环境来完成相应操作。 ## 配合工具添加指令 -把工作区通用指令写入 `AGENTS.md`,用 [skills](/zh-cn/docs/build/skills/)提供按任务加载的流程。Skill 可以告诉模型何时调用工具,Python 函数则定义工具执行的动作。 +把工作区通用指令写入 `AGENTS.md`,用 [skills](/zh-cn/docs/app/skills/)提供按任务加载的流程。Skill 可以告诉模型何时调用工具,Python 函数则定义工具执行的动作。 -逗号命令让使用者无需请求模型,就能调用已注册的工具。语法见[文本命令](/zh-cn/docs/build/tools/#commands)。 +逗号命令让使用者无需请求模型,就能调用已注册的工具。语法见[文本命令](/zh-cn/docs/sdk/tools/#commands)。 diff --git a/website/src/content/docs/zh-cn/docs/build/index.mdx b/website/src/content/docs/zh-cn/docs/build/index.mdx deleted file mode 100644 index 80f08e070..000000000 --- a/website/src/content/docs/zh-cn/docs/build/index.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: 定制 Bub 应用 -description: 通过工具、skills、插件和渠道扩展 Bub,或在自己的程序中嵌入应用处理流程。 -sidebar: - order: 0 ---- - -从能运行的 Bub 应用开始,添加工作中需要的行为。内置 `BubAgent` 扩展了 `core.Agent`;Framework 负责应用编排、插件 hooks 和渠道路由。 - -下面的指南沿用 Bub 应用的配置和扩展接口。如果你希望在 Python 程序中直接传入模型和工具,请从[构建自己的 Agent](/zh-cn/docs/build/sdk/)开始。 - -## 先运行应用 - -完成[应用安装](/zh-cn/docs/getting-started/install/#app-install),[运行第一轮对话](/zh-cn/docs/getting-started/first-turn/),再按需要[配置模型和工作区](/zh-cn/docs/operate/configure/)。 - -## 选择要添加的能力 - -| 你要完成的工作 | 从这里开始 | -| --- | --- | -| 让模型调用一个函数 | [Bub 应用中的工具](/zh-cn/docs/build/app-tools/) | -| 给模型提供可复用的操作说明 | [编写第一个 skill](/zh-cn/docs/getting-started/first-skill/),然后阅读 [Skills](/zh-cn/docs/build/skills/) | -| 打包集成或替换应用 hook | [编写第一个插件](/zh-cn/docs/getting-started/first-plugin/),然后阅读[插件](/zh-cn/docs/build/plugins/)与 [Hooks](/zh-cn/docs/build/hooks/) | -| 通过 CLI、Telegram 或其他适配器接收消息 | [渠道](/zh-cn/docs/operate/channels/) | -| 嵌入应用消息流程,或打包自己的发行版 | [嵌入与发行版](/zh-cn/docs/build/distribution/) | -| 持续运行 Bub 服务 | [部署](/zh-cn/docs/operate/deploy/) | - -工具、会话历史和 Agent 事件遵循[共同契约](/zh-cn/docs/concepts/)。应用还提供工具加载、代码模式选择等默认策略。[Bub 应用中的工具](/zh-cn/docs/build/app-tools/)说明这些策略;[应用轮次流程](/zh-cn/docs/concepts/turn-pipeline/)说明 Framework 如何连接消息接收与响应发送。 - -## 复用已有集成 - -- 在 [Bub Hub](https://hub.bub.build) 浏览插件、skills 与发行版。 -- 按照[集成指南](/zh-cn/docs/tutorials/)接入具体服务和协议。 -- 在[应用参考](/zh-cn/docs/reference/#application)中查阅签名与默认值。 diff --git a/website/src/content/docs/zh-cn/docs/build/plugins.mdx b/website/src/content/docs/zh-cn/docs/build/plugins.mdx deleted file mode 100644 index 597c02ea8..000000000 --- a/website/src/content/docs/zh-cn/docs/build/plugins.mdx +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: 打包插件 -description: 通过 Python 包,让 Bub 应用发现你的 hooks。 -sidebar: - order: 1 ---- - -把想要复用或分享的应用行为放进插件。Python 包在 `bub` 入口点组中声明目标,Bub 导入这个目标,并注册其中的 hook 实现。你也可以[在自己的服务中复用现有插件](/zh-cn/docs/build/reuse-plugins/),继续使用宿主自己的调用入口。 - -完整的入门例子见[编写第一个插件](/zh-cn/docs/getting-started/first-plugin/)。这篇指南说明打包自己的插件时,需要选择哪些形式。 - -可移植的 Agent Plugins 使用另一种格式,包括 `plugin.json`、skills 和 MCP servers。[Agent Plugins 集成](/zh-cn/docs/tutorials/agent-plugins/)通过一个 Python 插件加载这些包。 - -## 声明入口点 - -插件需要[安装 Bub](/zh-cn/docs/getting-started/install/)中说明的应用依赖。本指南使用 `bub[app]>=0.6.0` 作为包依赖。更新已有插件时,参见[升级到 0.6.0](/zh-cn/docs/reference/versions/)。 - -```toml -[project.entry-points."bub"] -myplugin = "bub_myplugin.plugin" -``` - -键 `myplugin` 是诊断信息中的插件名称,值是要导入的 Python 对象。 - -选择一种目标形式: - -| 目标 | 注册方式 | 适用情况 | -| --- | --- | --- | -| `bub_myplugin.plugin` | 注册模块 | hooks 是模块级函数 | -| `bub_myplugin.plugin:plugin` | 注册实例 | hooks 需要共享对象状态 | -| `bub_myplugin.plugin:MyPlugin` | 将 `BubFramework` 传给类或工厂,注册返回值 | hooks 需要访问 framework | - -模块形式在模块顶层定义 hooks: - -```python -from bub import hookimpl - - -@hookimpl -def build_prompt(message, session_id, state): - return "Describe this workspace." -``` - -实例形式在目标模块中创建对象: - -```python -from bub import hookimpl - - -class MyPlugin: - @hookimpl - def build_prompt(self, message, session_id, state): - return "Describe this workspace." - - -plugin = MyPlugin() -``` - -所有可调用目标都会收到 framework,类也一样。构造函数或工厂需要接收这个参数: - -```python -from bub import BubFramework, hookimpl - - -class MyPlugin: - def __init__(self, framework: BubFramework) -> None: - self.framework = framework - - @hookimpl - def build_prompt(self, message, session_id, state): - return f"Describe the workspace at {self.framework.workspace}." -``` - -这些例子会替换提示词。请在[改变应用行为](/zh-cn/docs/build/hooks/)中选择真正适合任务的 hook。 - -## 导入时注册工具 - -如果包内定义了 `@tool` 函数,在入口点模块中导入工具模块,确保 agent 创建前已完成注册: - -```python -from . import tools # noqa: F401 -``` - -应用创建 `BubAgent` 时会获取已注册工具的快照。[添加应用工具](/zh-cn/docs/build/app-tools/)会说明注册、延迟加载和代码模式。 - -## 安装到应用环境 - -可编辑安装允许你直接修改 Python 源码,无需重新构建包。安装目标必须是运行 Bub 的同一个环境。 - -使用 uv 项目时,在插件目录运行: - -```bash -uv sync -uv run bub hooks -``` - -如果安装到已有且已激活的应用环境,使用 `uv pip install -e .`,然后在该环境中运行 `bub hooks`。 - -`bub hooks` 可以确认注册结果。实际运行一次 hook 要处理的任务,再检查优先级和返回值。Bub 先注册内置 hooks,再注册入口点插件。同等优先级通常按注册顺序的逆序调用;`tryfirst` 和 `trylast` 会影响顺序。[调用规则](/zh-cn/docs/reference/hooks/#iteration-order)说明了哪个实现先运行。 - -如果插件没有出现,检查入口点组名、当前环境,以及启动日志中的导入错误。修改包元数据后,需要重新安装包。 - -## 分享插件 - -发布前,构建 wheel 并安装到独立环境,确认入口点能加载,并用一个实际任务检查 hook。如果要同时发布 skills 或多个插件,参见[打包 Bub 应用](/zh-cn/docs/build/distribution/)。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/philosophy.mdx b/website/src/content/docs/zh-cn/docs/concepts/design.mdx similarity index 90% rename from website/src/content/docs/zh-cn/docs/concepts/philosophy.mdx rename to website/src/content/docs/zh-cn/docs/concepts/design.mdx index 8eca21ee1..724557926 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/philosophy.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/design.mdx @@ -13,19 +13,19 @@ sidebar: `BubAgent` 增加应用的默认能力:设置、skills、命令、工具选择和运行中的消息引导。`BubFramework` 在外围管理插件、消息处理和出站路由。需要复用这些行为时,可以直接使用。 -[安装指南](/zh-cn/docs/getting-started/install/)说明依赖选择,[SDK 指南](/zh-cn/docs/build/sdk/)和[应用指南](/zh-cn/docs/build/)分别展示如何使用。 +[安装指南](/zh-cn/docs/getting-started/install/)说明依赖选择,[SDK 指南](/zh-cn/docs/sdk/)和[应用指南](/zh-cn/docs/app/)分别展示如何使用。 ## 分开保存历史与构建上下文 完整会话可能比下一步需要的上下文更长。Tape 保留已经记录的历史,上下文选择器决定模型读取什么。锚点让新的上下文从指定位置开始,同时保留此前的工作。 -这样既能查看保存的消息和工具结果,也能让下一次请求聚焦当前任务。历史是否持久保留仍取决于选择的存储,自定义应用状态也需要明确的保存方式。详见[会话历史与上下文](/zh-cn/docs/concepts/tape-and-context/)。 +这样既能查看保存的消息和工具结果,也能让下一次请求聚焦当前任务。历史是否持久保留仍取决于选择的存储,自定义应用状态也需要明确的保存方式。详见[会话历史与上下文](/zh-cn/docs/concepts/tape/)。 ## 明确扩展改变什么 工具描述一个动作,skill 描述一种工作方法,渠道把应用接到人们工作的地方。区分这些职责,才能找到适合当前任务的修改位置。 -运行时,事件提供模型与工具调用的接入点;应用 hooks 还负责配置、资源准备和消息路由。它们各自有执行顺序和错误规则,[扩展方式指南](/zh-cn/docs/concepts/surfaces/)会引导你找到对应接口。 +运行时,事件提供模型与工具调用的接入点;应用 hooks 还负责配置、资源准备和消息路由。它们各自有执行顺序和错误规则,[扩展方式指南](/zh-cn/docs/concepts/extension-points/)会引导你找到对应接口。 ## 背景阅读 diff --git a/website/src/content/docs/zh-cn/docs/concepts/events.mdx b/website/src/content/docs/zh-cn/docs/concepts/events.mdx index a4a53dda6..3d0cc26c1 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/events.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/events.mdx @@ -13,7 +13,7 @@ sidebar: | --- | --- | --- | | `AgentEvent` | 内部生命周期通知和调用拦截。 | `agent.events.on(...)` | | `StreamEvent` | 交给调用方的输出,例如文本和工具进度。 | 迭代 `agent.run()` 返回的流。 | -| `TapeEntry` | 保存的会话及部分运行记录。 | 查询 [Tape](/zh-cn/docs/concepts/tape-and-context/)。 | +| `TapeEntry` | 保存的会话及部分运行记录。 | 查询 [Tape](/zh-cn/docs/concepts/tape/)。 | Agent 循环控制执行过程,并在指定位置直接调用事件处理器。TapeRecorder 保存选定的交互和生命周期记录;其他需要保留的事件,可以由你的处理器写入 Tape。 @@ -21,7 +21,7 @@ Agent 循环控制执行过程,并在指定位置直接调用事件处理器 ## 注册处理器 -下面使用 [SDK 指南](/zh-cn/docs/build/sdk/)中的 `agent`: +下面使用 [SDK 指南](/zh-cn/docs/sdk/)中的 `agent`: ```python from bub.core.events import StepFinished, ToolCallFinished @@ -79,7 +79,7 @@ def require_refund_permission(event: ToolCallStarted) -> None: | `ChatFinished` | 本步骤的交互已准备好记录。 | 观察交互。 | | `CommandFinished` | 直接命令完成或失败。 | 观察命令、状态和输出。 | -命令 prompt 直接执行工具,产生工具事件和 `CommandFinished`。详见[命令](/zh-cn/docs/build/tools/#commands)。 +命令 prompt 直接执行工具,产生工具事件和 `CommandFinished`。详见[命令](/zh-cn/docs/sdk/tools/#commands)。 所有 agent 事件都携带本轮 `state`。继承 `TapeEvent` 的事件还携带当前 Tape。工具调用具有上下文时,可以通过 `event.call.context` 访问。 @@ -128,10 +128,10 @@ async def mark_reviewed() -> None: ## 从应用插件接入事件 -`AgentPlugins` 将 `before_llm_call`、`after_llm_call`、`before_tool_call`、`after_tool_call` hooks 接到调用事件上,将 `continue_prompt` hook 接到 `StepFinished` 上,将 steering inbox 接到 `MessagesPending` 上。插件按 hook 要求返回值,由适配器更新对应事件。BubAgent 使用这个适配器,核心 Agent [同样可以使用](/zh-cn/docs/build/reuse-plugins/#agent-plugins)。 +`AgentPlugins` 将 `before_llm_call`、`after_llm_call`、`before_tool_call`、`after_tool_call` hooks 接到调用事件上,将 `continue_prompt` hook 接到 `StepFinished` 上,将 steering inbox 接到 `MessagesPending` 上。插件按 hook 要求返回值,由适配器更新对应事件。BubAgent 使用这个适配器,核心 Agent [同样可以使用](/zh-cn/docs/sdk/app-plugins/#agent-plugins)。 该适配器遵循插件 hook 顺序,捕获单个拦截 hook 的异常,记录后跳过。拒绝调用应返回对应 decision。其他应用 hooks 遵循各自的错误处理规则。 -[应用 Hooks 指南](/zh-cn/docs/build/hooks/)提供插件示例,[Hooks 参考](/zh-cn/docs/reference/hooks/)列出顺序与签名。EventBus 包含在基础 SDK 安装中。 +[应用 Hooks 指南](/zh-cn/docs/app/hooks/)提供插件示例,[Hooks 参考](/zh-cn/docs/reference/hooks/)列出顺序与签名。EventBus 包含在基础 SDK 安装中。 `framework.events` 通过 `bub.application` 中的 `CommandChanged`、`WorkChanged` 和 `PromptChanged` 发布命令、通道工作和用户提示,沿用相同的 `on()` 与 `off()` 接口订阅和取消订阅。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/surfaces.mdx b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx similarity index 56% rename from website/src/content/docs/zh-cn/docs/concepts/surfaces.mdx rename to website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx index 267932dd0..3ad6b278f 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/surfaces.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx @@ -9,25 +9,25 @@ sidebar: | 想完成什么 | 使用什么 | 属于哪一层 | | --- | --- | --- | -| 让 Agent 查询或修改内容 | [工具](/zh-cn/docs/build/tools/)。 | 公共内核能力。直接传给 Agent,或在应用中注册。 | +| 让 Agent 查询或修改内容 | [工具](/zh-cn/docs/sdk/tools/)。 | 公共内核能力。直接传给 Agent,或在应用中注册。 | | 观察或拦截模型、工具调用 | [事件处理器](/zh-cn/docs/concepts/events/)。 | Core EventBus;应用插件也可以使用对应的拦截 hooks。 | -| 改变哪些历史进入模型消息 | [TapeContext](/zh-cn/docs/concepts/tape-and-context/#context)。 | 直接传给 Agent,或通过应用 hook 提供。 | -| 教会 Agent 一种可复用的工作方法 | [skill](/zh-cn/docs/build/skills/)。 | 由 Bub 应用发现和加载。 | -| 改变应用初始化或消息处理 | [插件](/zh-cn/docs/build/plugins/)。 | BubFramework 及其 hooks。 | -| 接入消息服务 | [渠道](/zh-cn/docs/operate/channels/)。 | 应用的入站与出站路由。 | +| 改变哪些历史进入模型消息 | [TapeContext](/zh-cn/docs/concepts/tape/#context)。 | 直接传给 Agent,或通过应用 hook 提供。 | +| 教会 Agent 一种可复用的工作方法 | [skill](/zh-cn/docs/app/skills/)。 | 由 Bub 应用发现和加载。 | +| 改变应用初始化或消息处理 | [插件](/zh-cn/docs/app/plugins/)。 | BubFramework 及其 hooks。 | +| 接入消息服务 | [渠道](/zh-cn/docs/app/gateway/)。 | 应用的入站与出站路由。 | ## 动作与指令 工具执行 Python 并返回结果。skill 是包含 `SKILL.md` 及可选辅助文件的目录。它的指令可以说明怎样使用工具,其中提到的 Python 工具实现需要单独提供。 -普通 Agent 使用你提供的工具和提示词片段。Bub 应用负责发现 skills 和读取 `AGENTS.md`,详见 [Skills](/zh-cn/docs/build/skills/)和[应用工具](/zh-cn/docs/build/app-tools/)。 +普通 Agent 使用你提供的工具和提示词片段。Bub 应用负责发现 skills 和读取 `AGENTS.md`,详见 [Skills](/zh-cn/docs/app/skills/)和[应用工具](/zh-cn/docs/app/tools/)。 -工具也可以通过[文本命令](/zh-cn/docs/build/tools/#commands)调用,或由 CodeMode 中生成的 Python 调用。这些调用路径共用同一个工具实现。 +工具也可以通过[文本命令](/zh-cn/docs/sdk/tools/#commands)调用,或由 CodeMode 中生成的 Python 调用。这些调用路径共用同一个工具实现。 ## 消息与应用状态 渠道接收入站 envelope 并发送出站 envelope。应用从中读取内容、路由信息和会话标识。各 hooks 共用当前轮次的可变 `TurnState` 字典。 -[应用消息处理流程](/zh-cn/docs/concepts/turn-pipeline/)先准备状态和 prompt,再调用 Agent。直接使用 SDK 时,由调用方把 prompt 和 state 传给 Agent。 +[应用消息处理流程](/zh-cn/docs/concepts/turn/)先准备状态和 prompt,再调用 Agent。直接使用 SDK 时,由调用方把 prompt 和 state 传给 Agent。 -按需要的能力选择依赖,按宿主的处理方式选择调用入口。Python 服务可以复用应用插件,同时保留自己的请求处理和流接口。[在 Agent 中复用插件](/zh-cn/docs/build/reuse-plugins/)会说明怎样直接使用插件组件,以及如何在宿主中接入 framework hooks。 +按需要的能力选择依赖,按宿主的处理方式选择调用入口。Python 服务可以复用应用插件,同时保留自己的请求处理和流接口。[在 Agent 中复用插件](/zh-cn/docs/sdk/app-plugins/)会说明怎样直接使用插件组件,以及如何在宿主中接入 framework hooks。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/index.mdx b/website/src/content/docs/zh-cn/docs/concepts/index.mdx index 08142ba28..715f56ef6 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/index.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/index.mdx @@ -5,14 +5,14 @@ sidebar: order: 0 --- -两条路线使用相同的工具、会话历史和事件接口。需要理解组件或选择接入方式时,可以查阅这些页面。也可以先从[自己的 Agent](/zh-cn/docs/build/sdk/) 或 [Bub 应用](/zh-cn/docs/build/)开始,遇到具体问题再回到这里。 +两条路线使用相同的工具、会话历史和事件接口。需要理解组件或选择接入方式时,可以查阅这些页面。也可以先从[自己的 Agent](/zh-cn/docs/sdk/) 或 [Bub 应用](/zh-cn/docs/app/)开始,遇到具体问题再回到这里。 | 想了解什么 | 阅读 | | --- | --- | -| 内核与应用如何配合 | [设计选择](/zh-cn/docs/concepts/philosophy/) | -| 当前任务适合哪个扩展点 | [选择扩展方式](/zh-cn/docs/concepts/surfaces/) | -| 怎样定义动作或使用生成的 Python | [工具与 CodeMode](/zh-cn/docs/build/tools/) | -| 保存哪些信息,模型读取哪些信息 | [会话历史与上下文](/zh-cn/docs/concepts/tape-and-context/) | +| 内核与应用如何配合 | [设计选择](/zh-cn/docs/concepts/design/) | +| 当前任务适合哪个扩展点 | [选择扩展方式](/zh-cn/docs/concepts/extension-points/) | +| 怎样定义动作或使用生成的 Python | [工具与 CodeMode](/zh-cn/docs/sdk/tools/) | +| 保存哪些信息,模型读取哪些信息 | [会话历史与上下文](/zh-cn/docs/concepts/tape/) | | 怎样观察、修改或停止调用 | [事件与调用控制](/zh-cn/docs/concepts/events/) | ## 几个常用术语 @@ -21,4 +21,4 @@ sidebar: **Tape** 保存会话记录,**条目(entry)**包括消息、工具调用、结果和事件。**锚点(anchor)**标记上下文的起点,**handoff** 写入新锚点。本轮可变**状态(state)**是另一个字典,由工具和处理器共用。 -Bub 应用还提供用于接入 Python hooks 的**插件**、提供可复用指令的 **skills**,以及收发消息的**渠道**。[应用消息处理流程](/zh-cn/docs/concepts/turn-pipeline/)将这些组件连接到 Agent 周围。 +Bub 应用还提供用于接入 Python hooks 的**插件**、提供可复用指令的 **skills**,以及收发消息的**渠道**。[应用消息处理流程](/zh-cn/docs/concepts/turn/)将这些组件连接到 Agent 周围。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/tape-and-context.mdx b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx similarity index 94% rename from website/src/content/docs/zh-cn/docs/concepts/tape-and-context.mdx rename to website/src/content/docs/zh-cn/docs/concepts/tape.mdx index 46a5bfc8c..709186662 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/tape-and-context.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx @@ -97,13 +97,13 @@ async def start_next_phase() -> None: 自己管理一段工作时,可以使用 `agent.session(name, merge_back=...)` 或 `tape.fork_tape(merge_back=...)`。两者默认都在退出时合并,包括异常退出。 -如果同一 Tape 上的运行需要看到前一轮的完整结果,应让它们依次执行。会话锁由你的应用提供。[FastAPI 示例](/zh-cn/docs/tutorials/fastapi/)展示单进程内的锁;多个 worker 需要跨进程协调。 +如果同一 Tape 上的运行需要看到前一轮的完整结果,应让它们依次执行。会话锁由你的应用提供。[FastAPI 示例](/zh-cn/docs/sdk/web-service/)展示单进程内的锁;多个 worker 需要跨进程协调。 ## 将大型结果放在会话旁边 `TapeSidecar` 用独立的 Tape 保存关联数据。挂载的 sidecar 随主 Tape 一起分支、合并和重置,其条目不进入主上下文。自己的应用需要这项能力时,可以通过 `Agent(sidecars=...)` 显式传入。 -Bub 应用为大型工具结果挂载 spill sidecar。会话中保留有限预览和句柄,Agent 可以通过 `spill.read` 分段读取。合并分支或重置 Tape 时,sidecar 的失败会单独记录,主 Tape 操作仍可完成。应用的 spill 限制属于[配置](/zh-cn/docs/operate/configure/)。 +Bub 应用为大型工具结果挂载 spill sidecar。会话中保留有限预览和句柄,Agent 可以通过 `spill.read` 分段读取。合并分支或重置 Tape 时,sidecar 的失败会单独记录,主 Tape 操作仍可完成。应用的 spill 限制属于[配置](/zh-cn/docs/app/configure/)。 @@ -111,6 +111,6 @@ Bub 应用为大型工具结果挂载 spill sidecar。会话中保留有限预 BubAgent 使用 BubFramework 提供的存储。内置应用使用 Bub home 下的 `FuzzyFileTapeStore`,通常位于 `~/.bub/tapes/`。Tape 名称由工作区路径和会话 ID 的哈希组合而成,因此不同工作区中相同的会话 ID 拥有独立历史。 -应用通过 `provide_tape_store` 替换存储,通过 `build_tape_context` 选择上下文。[SQLAlchemy 指南](/zh-cn/docs/tutorials/tapestore-sqlalchemy/)展示这条插件路线。直接使用 SDK 的服务则按本页前面的方式,把实现传给 `Agent(store=...)`。 +应用通过 `provide_tape_store` 替换存储,通过 `build_tape_context` 选择上下文。[SQLAlchemy 指南](/zh-cn/docs/integrations/storage/)展示这条插件路线。直接使用 SDK 的服务则按本页前面的方式,把实现传给 `Agent(store=...)`。 内置应用会从 Tape 记录恢复选定的会话设置。其他本轮状态需要单独实现持久化。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/turn-pipeline.mdx b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx similarity index 93% rename from website/src/content/docs/zh-cn/docs/concepts/turn-pipeline.mdx rename to website/src/content/docs/zh-cn/docs/concepts/turn.mdx index 84b9c07a3..d471a687e 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/turn-pipeline.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx @@ -45,4 +45,4 @@ BubFramework 将 Agent 接到应用所需的会话标识、本轮状态、prompt ## 扩展应用 -按照[插件指南](/zh-cn/docs/build/plugins/)注册实现,通过 [Hooks 示例](/zh-cn/docs/build/hooks/)完成具体修改,精确签名和顺序见 [Hooks 参考](/zh-cn/docs/reference/hooks/)。 +按照[插件指南](/zh-cn/docs/app/plugins/)注册实现,通过 [Hooks 示例](/zh-cn/docs/app/hooks/)完成具体修改,精确签名和顺序见 [Hooks 参考](/zh-cn/docs/reference/hooks/)。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/first-plugin.mdx b/website/src/content/docs/zh-cn/docs/getting-started/first-plugin.mdx deleted file mode 100644 index 0ccb0cb23..000000000 --- a/website/src/content/docs/zh-cn/docs/getting-started/first-plugin.mdx +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: 编写第一个插件 -description: 安装一个简单的回声插件,确认 Bub 会使用它。 -sidebar: - order: 4 ---- - -插件可以改变 Bub 应用处理任务的方式。这个插件用回声回复替代模型阶段,让你无需模型凭据就能检查插件发现和轮次处理。需要内置 agent 的模型调用与会话记录时,保留它的模型实现。 - -## 开始之前 - -按[安装 Bub](/zh-cn/docs/getting-started/install/)中的说明准备 Python 3.12+ 和 uv。这篇教程会创建一个同时包含 Bub 0.6.0 和插件的项目环境。 - -## 创建包 - -创建以下包目录: - -```text -bub-echo-plugin/ -├── pyproject.toml -└── src/ - └── bub_echo_plugin/ - ├── __init__.py - └── plugin.py -``` - -将 `__init__.py` 留空。在 `pyproject.toml` 中写入: - -```toml -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "bub-echo-plugin" -version = "0.1.0" -requires-python = ">=3.12" -dependencies = ["bub[app]>=0.6.0"] - -[project.entry-points."bub"] -echo = "bub_echo_plugin.plugin:echo_plugin" - -[tool.hatch.build.targets.wheel] -packages = ["src/bub_echo_plugin"] -``` - -`bub` 入口点组告诉应用到哪里寻找插件。 - -## 实现回复 - -在 `src/bub_echo_plugin/plugin.py` 中写入: - -```python -from bub import hookimpl - - -class EchoPlugin: - @hookimpl - def run_model(self, prompt, session_id, state): - return f"[echo:{session_id}] {prompt}" - - -echo_plugin = EchoPlugin() -``` - -应用仍会构建收到的提示词。你的 `run_model` 实现提供回复,替代内置 agent 的执行。 - -## 安装并试运行 - -在插件目录安装项目和依赖: - -```bash -uv sync -uv run bub hooks -``` - -`uv sync` 会以可编辑模式安装这个包。在同一个项目中通过 `uv run` 运行 Bub,它就能发现插件。 - -报告应在 `run_model` 下列出 `echo`。内置 agent 则出现在 `run_model_stream` 下。 - -```bash -uv run bub run "hello from my plugin" -``` - -没有其他插件覆盖模型阶段时,回复包含: - -```text -[echo:cli:local] hello from my plugin -``` - -如果没有看到 `echo`,检查安装插件的环境是否就是运行 `bub` 的环境。如果已经列出却返回其他回复,检查其他模型 hooks 及其[优先级](/zh-cn/docs/reference/hooks/#iteration-order)。 - -- [打包插件](/zh-cn/docs/build/plugins/)说明发现机制和入口点形式。 -- [改变应用行为](/zh-cn/docs/build/hooks/)帮助你选择适合当前任务的 hook。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/first-skill.mdx b/website/src/content/docs/zh-cn/docs/getting-started/first-skill.mdx deleted file mode 100644 index 04622728e..000000000 --- a/website/src/content/docs/zh-cn/docs/getting-started/first-skill.mdx +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: 添加项目 skill -description: 为 Bub 编写可复用的流程,并确认它能加载这些指令。 -sidebar: - order: 3 ---- - -skill 为 Bub 提供完成特定任务的指令。这个例子会让它在修改代码前先了解仓库结构。 - -使用[首次运行 Bub](/zh-cn/docs/getting-started/first-turn/)中准备的工作区。每个 skill 包含一个 `SKILL.md` 文件,也可以在旁边放置脚本或参考资料。 - -## 编写指令 - -创建 skill 目录: - -```bash -mkdir -p .agents/skills/repo-map -``` - -创建 `.agents/skills/repo-map/SKILL.md`: - -```markdown ---- -name: repo-map -description: 修改代码前,先阅读本地仓库结构。 ---- - -1. 运行 `rg --files`,查看仓库结构。 -2. 阅读能够说明本次修改的文件。 -3. 编辑代码前,说明会涉及哪些模块。 -``` - -`name` 必须与目录名一致,使用连字符分隔的小写单词。description 帮助模型判断何时使用这个 skill。 - -## 检查能否加载 - -```bash -bub run ",skill name=repo-map" -``` - -命令会打印 skill 的指令,不调用模型。如果返回 `(no such skill)`,检查文件路径和 YAML frontmatter。 - -## 在对话中使用 - -配置好模型后,运行: - -```bash -bub run '使用 $repo-map skill,然后列出你找到的顶层模块。' -``` - -`$repo-map` 会把该 skill 的指令加入本轮系统提示词。模型随后使用应用中可用的工具执行这些步骤。这个例子需要在执行环境中安装 `rg`。 - -你可以把 skill 留在项目中,也可以[打包分享给其他人](/zh-cn/docs/build/skills/)。打包指南还会说明发现顺序和配套文件的用法。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx index daf0689d0..5a9c45653 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx @@ -7,17 +7,17 @@ sidebar: 把 Agent 接入自己的 Python 程序,或从现成的 Bub 应用开始定制。Bub 应用使用同一个 Agent 内核,并提供配置、插件和渠道接入。 -## 构建自己的 Agent +## 使用 Bub 应用 -选择模型和工具,用 `bub.core.Agent` 组合 Agent,再接入自己的程序。先定义一个查询订单状态的工具,让 Agent 调用一次。 +先运行 Bub,再通过配置和插件添加需要的行为。这条路线也包含 skills、渠道和部署。 -[构建自己的 Agent](/zh-cn/docs/build/sdk/)会带你跑通这个 Python 示例。需要 HTTP 接口时,继续阅读 [FastAPI 接入](/zh-cn/docs/tutorials/fastapi/)。 +从[运行第一轮对话](/zh-cn/docs/app/first-session/)开始;应用跑通后,到[使用 Bub 应用](/zh-cn/docs/app/)选择下一步要扩展的部分。 -## 定制 Bub 应用 +## 构建你的 Bub Agent -先运行 Bub,再通过配置和插件添加需要的行为。这条路线也包含 skills、渠道和部署。 +选择模型和工具,用 `bub.core.Agent` 组合 Agent,再接入自己的程序。先定义一个查询订单状态的工具,让 Agent 调用一次。 -从[运行第一轮对话](/zh-cn/docs/getting-started/first-turn/)开始;应用跑通后,到[定制 Bub 应用](/zh-cn/docs/build/)选择下一步要扩展的部分。 +[构建你的 Bub Agent](/zh-cn/docs/sdk/)会带你跑通这个 Python 示例。需要 HTTP 接口时,继续阅读 [FastAPI 接入](/zh-cn/docs/sdk/web-service/)。 ## 用到时,再查共同机制 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx index 6d2cd1886..1ff7e61e0 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx @@ -22,7 +22,7 @@ uv run python -c "from bub.core import Agent; print(Agent.__name__)" 如果还没有项目,先运行 `uv init --python 3.12 my-agent`,再进入 `cd my-agent`。 -基础安装包含内核所需的 Republic、Pydantic、Loguru 和 typing-extensions。导入检查无需模型凭据。接着阅读[构建自己的 Agent](/zh-cn/docs/build/sdk/),在 Python 中配置模型并运行第一个示例。 +基础安装包含内核所需的 Republic、Pydantic、Loguru 和 typing-extensions。导入检查无需模型凭据。接着阅读[构建自己的 Agent](/zh-cn/docs/sdk/),在 Python 中配置模型并运行第一个示例。 @@ -65,7 +65,7 @@ uv tool update-shell bub --help ``` -接着阅读[首次运行 Bub](/zh-cn/docs/getting-started/first-turn/),在自己的工作区开始对话。 +接着阅读[首次运行 Bub](/zh-cn/docs/app/first-session/),在自己的工作区开始对话。 基础安装与 `bub[app]` 包含相同的包文件;extra 改变的是安装的依赖。缺少应用依赖时,`bub` 命令会退出,并提示安装 `bub[app]`。 diff --git a/website/src/content/docs/zh-cn/docs/tutorials/acp-server.mdx b/website/src/content/docs/zh-cn/docs/integrations/acp.mdx similarity index 93% rename from website/src/content/docs/zh-cn/docs/tutorials/acp-server.mdx rename to website/src/content/docs/zh-cn/docs/integrations/acp.mdx index f139390d8..dca386c7c 100644 --- a/website/src/content/docs/zh-cn/docs/tutorials/acp-server.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/acp.mdx @@ -2,6 +2,7 @@ title: 接入 ACP 编辑器 description: 安装 bub-acp-server,并把 Bub 接入 Zed 等支持 ACP 的编辑器。 sidebar: + badge: { text: '应用', variant: 'note' } order: 6 --- @@ -128,6 +129,6 @@ HELLO ## 下一步 -- [配置](/zh-cn/docs/operate/configure/):Bub 的模型和运行时设置。 -- [应用运行流程](/zh-cn/docs/concepts/turn-pipeline/):Bub 如何把入站消息转成模型调用和出站消息。 -- [构建插件](/zh-cn/docs/build/plugins/):用 hooks、tools 和渠道扩展 Bub。 +- [配置](/zh-cn/docs/app/configure/):Bub 的模型和运行时设置。 +- [应用运行流程](/zh-cn/docs/concepts/turn/):Bub 如何把入站消息转成模型调用和出站消息。 +- [构建插件](/zh-cn/docs/app/plugins/):用 hooks、tools 和渠道扩展 Bub。 diff --git a/website/src/content/docs/zh-cn/docs/tutorials/agent-plugins.mdx b/website/src/content/docs/zh-cn/docs/integrations/agent-plugins.mdx similarity index 94% rename from website/src/content/docs/zh-cn/docs/tutorials/agent-plugins.mdx rename to website/src/content/docs/zh-cn/docs/integrations/agent-plugins.mdx index 9527ff226..d9a910bf9 100644 --- a/website/src/content/docs/zh-cn/docs/tutorials/agent-plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/agent-plugins.mdx @@ -2,6 +2,7 @@ title: 加载 Agent Plugins description: 加载包含 skill 和 MCP 服务器的可移植 Agent Plugin,与现有 Bub 扩展一起使用。 sidebar: + badge: { text: '应用', variant: 'note' } order: 4 --- @@ -11,7 +12,7 @@ sidebar: 接入插件位于 [`bub-contrib`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-agent-plugins)。 -Bub 的 Python 插件仍通过[Python 插件入口点](/zh-cn/docs/build/plugins/)加载。 +Bub 的 Python 插件仍通过[Python 插件入口点](/zh-cn/docs/app/plugins/)加载。 ## 前提条件 @@ -126,7 +127,7 @@ bub --workspace . chat 该命令应返回当前时间。启动过程是异步的,请等待几秒再调用工具;首次启动时,`uvx` 可能还需要下载时间服务器。 如果看到 shell 的 `command not found` 错误,说明工具尚未注册;请查看启动日志中是否有连接错误,等启动完成后重试。 -[配置模型](/zh-cn/docs/operate/configure/)后,也可以让 Bub 调用时间工具,或使用 `$plugin-summary` 总结笔记。 +[配置模型](/zh-cn/docs/app/configure/)后,也可以让 Bub 调用时间工具,或使用 `$plugin-summary` 总结笔记。 ## 与现有 MCP 服务器一起使用 @@ -204,6 +205,6 @@ stdio 服务器会收到绝对路径形式的 `PLUGIN_ROOT` 和 `PLUGIN_DATA` ## 下一步 -- [连接 MCP 服务器](/zh-cn/docs/tutorials/mcp/):管理 Bub 数据目录配置中的服务器。 -- [构建 skills](/zh-cn/docs/build/skills/):编写并验证 Bub skills。 -- [构建插件](/zh-cn/docs/build/plugins/):使用 Python 包扩展 Bub hooks。 +- [连接 MCP 服务器](/zh-cn/docs/integrations/mcp/):管理 Bub 数据目录配置中的服务器。 +- [构建 skills](/zh-cn/docs/app/skills/):编写并验证 Bub skills。 +- [构建插件](/zh-cn/docs/app/plugins/):使用 Python 包扩展 Bub hooks。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/index.mdx b/website/src/content/docs/zh-cn/docs/integrations/index.mdx new file mode 100644 index 000000000..fe91a262e --- /dev/null +++ b/website/src/content/docs/zh-cn/docs/integrations/index.mdx @@ -0,0 +1,26 @@ +--- +title: 把 Bub 接入你的工具 +description: 接入 HTTP 服务、编辑器、模型服务或应用扩展。 +sidebar: + order: 0 +--- + +按你使用 Bub 的方式选择接入指南。每篇指南会说明所需组件,并给出检查结果的方法。 + +## 构建自己的 Agent + +- [用 FastAPI 提供 Agent 服务](/zh-cn/docs/sdk/web-service/):把 `core.Agent` 接入 HTTP 服务,由服务管理会话存储。 + +## 定制 Bub 应用 + +这些指南使用应用的 CLI、配置或插件机制。先[安装 Bub 应用](/zh-cn/docs/getting-started/install/),把 Bub 和插件放在同一个环境中。 + +- [连接 MCP 服务器](/zh-cn/docs/integrations/mcp/):调用 MCP 服务器提供的工具。 +- [加载 Agent Plugins](/zh-cn/docs/integrations/agent-plugins/):加载包含 skills 和 MCP 服务器的可移植插件包。 +- [接入 ACP 编辑器](/zh-cn/docs/integrations/acp/):在 Zed 等编辑器中使用 Bub。 +- [使用本地 llama.cpp 模型](/zh-cn/docs/integrations/local-models/):让应用调用本地模型服务。 +- [把会话历史存入 SQLite](/zh-cn/docs/integrations/storage/):通过插件替换应用的文件存储。 +- [检查会话记录和执行轨迹](/zh-cn/docs/integrations/observability/):查看本地记录,或将轨迹导出到 Phoenix 和 Logfire。 +- [用其他语言实现 hooks](/zh-cn/docs/app/language-extensions/):尝试实验性的 WebAssembly 接入。 + +各集成指南都提供插件文档链接,可据此查看该插件的具体配置方式。 diff --git a/website/src/content/docs/zh-cn/docs/tutorials/local-llama-cpp.mdx b/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx similarity index 98% rename from website/src/content/docs/zh-cn/docs/tutorials/local-llama-cpp.mdx rename to website/src/content/docs/zh-cn/docs/integrations/local-models.mdx index 07fcf3781..140892dc5 100644 --- a/website/src/content/docs/zh-cn/docs/tutorials/local-llama-cpp.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx @@ -2,6 +2,7 @@ title: 使用本地 llama.cpp 模型运行 Bub description: 启动本地 llama.cpp 服务,并把 Bub 配置为使用这个 OpenAI 兼容模型后端。 sidebar: + badge: { text: '通用', variant: 'success' } order: 3 --- diff --git a/website/src/content/docs/zh-cn/docs/tutorials/mcp.mdx b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx similarity index 90% rename from website/src/content/docs/zh-cn/docs/tutorials/mcp.mdx rename to website/src/content/docs/zh-cn/docs/integrations/mcp.mdx index 5984b187e..49613d1a9 100644 --- a/website/src/content/docs/zh-cn/docs/tutorials/mcp.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx @@ -2,12 +2,13 @@ title: 连接 MCP 服务器 description: 安装 bub-mcp 插件,注册一个时间 MCP 服务器,并在 Bub 轮次中调用其工具。 sidebar: + badge: { text: '应用', variant: 'note' } order: 2 --- 通过 `bub-mcp` 插件,把 MCP 服务器接入 Bub 应用。本指南注册一个时间服务器,验证连接,再调用它提供的工具。 -这条接入路径使用应用的插件机制和生命周期。工具的共同定义见[工具](/zh-cn/docs/build/tools/)。 +这条接入路径使用应用的插件机制和生命周期。工具的共同定义见[工具](/zh-cn/docs/sdk/tools/)。 ## 前提条件 @@ -27,7 +28,7 @@ bub install bub-mcp@main `bub install` 使用当前 Bub 所在的环境。插件来源与依赖处理方式见 [CLI 参考](/zh-cn/docs/reference/cli/#bub-install)。 -[Agent Plugins](/zh-cn/docs/tutorials/agent-plugins/) 也可以加载 skills 和自带的 MCP 服务器,与这里的服务器一起使用。该集成使用独立的生命周期渠道,以及包含插件名的工具名称。 +[Agent Plugins](/zh-cn/docs/integrations/agent-plugins/) 也可以加载 skills 和自带的 MCP 服务器,与这里的服务器一起使用。该集成使用独立的生命周期渠道,以及包含插件名的工具名称。 验证插件已加载: @@ -108,7 +109,7 @@ bub gateway --enable-channel cli --enable-channel mcp.lifecycle 如果模型没有调用工具就回答了,先确认服务器已启动,再明确要求它使用时间工具。MCP 在后台启动,第一轮对话可能早于工具就绪。 -长期运行的部署请参见 [部署](/zh-cn/docs/operate/deploy/),容器镜像跑的就是同样的网关命令。 +长期运行的部署请参见 [部署](/zh-cn/docs/app/deploy/),容器镜像跑的就是同样的网关命令。 ## 添加其他类型的服务器 @@ -182,6 +183,6 @@ bub mcp remove time ## 下一步 -- [加载 Agent Plugins](/zh-cn/docs/tutorials/agent-plugins/):在 `mcp.json` 之外加载可移植的 skills 和 MCP 服务器。 -- [配置](/zh-cn/docs/operate/configure/):Bub 的配置层与环境变量。 +- [加载 Agent Plugins](/zh-cn/docs/integrations/agent-plugins/):在 `mcp.json` 之外加载可移植的 skills 和 MCP 服务器。 +- [配置](/zh-cn/docs/app/configure/):Bub 的配置层与环境变量。 - [bub-mcp 源码](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-mcp):插件完整源码与高级选项。 diff --git a/website/src/content/docs/zh-cn/docs/tutorials/observability.mdx b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx similarity index 97% rename from website/src/content/docs/zh-cn/docs/tutorials/observability.mdx rename to website/src/content/docs/zh-cn/docs/integrations/observability.mdx index dc5c43c29..8cdc42026 100644 --- a/website/src/content/docs/zh-cn/docs/tutorials/observability.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx @@ -2,6 +2,7 @@ title: 检查应用运行 description: 检查会话记录,并把模型与工具调用轨迹导出到 Phoenix 或 Logfire。 sidebar: + badge: { text: '通用', variant: 'success' } order: 1 --- @@ -54,7 +55,7 @@ last_token_cache_hit_rate: 72.50% - `last_token_usage` 会在模型路径记录 token usage 后出现。 - `last_token_cache_hit_rate` 表示同一次模型调用中命中缓存的提示词 token 比例(提供商提供明细时可用)。 -Tape 保存对话条目和选定的运行记录,上下文选择器决定哪些条目进入模型输入。`,tape.info` 和 `,tape.search` 用于检查这些记录,持久化边界见 [Tape 与上下文](/zh-cn/docs/concepts/tape-and-context/)。 +Tape 保存对话条目和选定的运行记录,上下文选择器决定哪些条目进入模型输入。`,tape.info` 和 `,tape.search` 用于检查这些记录,持久化边界见 [Tape 与上下文](/zh-cn/docs/concepts/tape/)。 ## 2. 在 tape 中搜索症状 @@ -198,4 +199,4 @@ docker rm -f bub-phoenix ## 下一步 -- [Tape 与上下文](/zh-cn/docs/concepts/tape-and-context/):理解 Bub 记录什么,以及如何重建上下文。 +- [Tape 与上下文](/zh-cn/docs/concepts/tape/):理解 Bub 记录什么,以及如何重建上下文。 diff --git a/website/src/content/docs/zh-cn/docs/tutorials/tapestore-sqlalchemy.mdx b/website/src/content/docs/zh-cn/docs/integrations/storage.mdx similarity index 87% rename from website/src/content/docs/zh-cn/docs/tutorials/tapestore-sqlalchemy.mdx rename to website/src/content/docs/zh-cn/docs/integrations/storage.mdx index 636a48c90..ab2498640 100644 --- a/website/src/content/docs/zh-cn/docs/tutorials/tapestore-sqlalchemy.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/storage.mdx @@ -2,12 +2,13 @@ title: 把应用会话历史存入 SQLite description: 通过存储插件,把 Bub 应用的会话历史从文件改存到 SQLite。 sidebar: + badge: { text: '通用', variant: 'success' } order: 2 --- 通过 [`bub-tapestore-sqlalchemy`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-tapestore-sqlalchemy),把 Bub 应用的会话历史存入本地 SQLite 数据库。插件替换应用默认的文件存储,你仍可用 `,tape.info` 和 `,tape.search` 检查记录。 -本指南通过 `BUB_*` 设置为 Bub CLI 配置插件。在自己的 Python 程序中,可以构造 `SQLAlchemyTapeStore` 并直接传给 `core.Agent`,具体用法见[在 Agent 中复用插件](/zh-cn/docs/build/reuse-plugins/)。[Tape 与上下文](/zh-cn/docs/concepts/tape-and-context/)说明两种用法共用的存储行为。 +本指南通过 `BUB_*` 设置为 Bub CLI 配置插件。在自己的 Python 程序中,可以构造 `SQLAlchemyTapeStore` 并直接传给 `core.Agent`,具体用法见[在 Agent 中复用插件](/zh-cn/docs/sdk/app-plugins/)。[Tape 与上下文](/zh-cn/docs/concepts/tape/)说明两种用法共用的存储行为。 ## 前置条件 @@ -110,6 +111,6 @@ rm -f "$PWD/bub-tapes.db" ## 下一步 -- [使用 Tape 与 Phoenix 观察 Bub](/zh-cn/docs/tutorials/observability/):把持久化 Tape 和 GenAI 遥测配合使用。 -- [Tape 与上下文](/zh-cn/docs/concepts/tape-and-context/):理解 Bub 记录什么,以及如何重建上下文。 -- [插件](/zh-cn/docs/build/plugins/):`bub-tapestore-sqlalchemy` 遵循的插件契约。 +- [使用 Tape 与 Phoenix 观察 Bub](/zh-cn/docs/integrations/observability/):把持久化 Tape 和 GenAI 遥测配合使用。 +- [Tape 与上下文](/zh-cn/docs/concepts/tape/):理解 Bub 记录什么,以及如何重建上下文。 +- [插件](/zh-cn/docs/app/plugins/):`bub-tapestore-sqlalchemy` 遵循的插件契约。 diff --git a/website/src/content/docs/zh-cn/docs/operate/index.mdx b/website/src/content/docs/zh-cn/docs/operate/index.mdx deleted file mode 100644 index d36fb6053..000000000 --- a/website/src/content/docs/zh-cn/docs/operate/index.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: 运行 Bub 应用 -description: 配置 Bub 应用、接入渠道,并把它部署为服务。 -sidebar: - order: 0 ---- - -完成[第一轮对话](/zh-cn/docs/getting-started/first-turn/)后,选择日常使用 Bub 的方式:留在终端里工作,接入 Telegram 机器人,或在服务器上运行网关。 - -这些指南使用 Bub 应用及其配置、插件和渠道。先按[安装指南](/zh-cn/docs/getting-started/install/)准备应用依赖。 - -## 选择下一步 - -- [配置应用](/zh-cn/docs/operate/configure/):选择模型,设置提供商凭据。 -- [接入渠道](/zh-cn/docs/operate/channels/):选择接收消息的服务,以及消息如何合并为一轮对话。 -- [使用终端](/zh-cn/docs/operate/channels/cli/):执行一个任务,或继续交互式对话。 -- [接入 Telegram](/zh-cn/docs/operate/channels/telegram/):设置机器人,选择允许使用它的人。 -- [部署应用](/zh-cn/docs/operate/deploy/):构建容器,在重启后保留数据。 -- [检查一次运行](/zh-cn/docs/tutorials/observability/):查看会话历史,导出执行轨迹。 - -如果你的服务直接管理 `core.Agent`,请沿[用 FastAPI 提供 Agent 服务](/zh-cn/docs/tutorials/fastapi/)继续。该路线在 Python 中管理模型和存储。 diff --git a/website/src/content/docs/zh-cn/docs/reference/cli.mdx b/website/src/content/docs/zh-cn/docs/reference/cli.mdx index 909a73df9..f19b9581c 100644 --- a/website/src/content/docs/zh-cn/docs/reference/cli.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/cli.mdx @@ -21,7 +21,7 @@ bub [OPTIONS] COMMAND [ARGS]... 不带命令运行 `bub` 时,会以默认参数执行 `BubFramework.default_command`。内置插件把它设为 `chat`,因此 `bub` 和 `bub -w PATH` 会启动聊天 REPL。插件可以在 `register_cli_commands` hook 中设置 `default_command` 来改用其他命令;只有在没有插件设置时,内置插件才会填入 `chat`。 -支持 [OSC 7501](https://www.superlogical.com/rex/docs/build/program-status) 的终端可以显示 Bub 的运行、等待输入或授权、完成和失败状态。取消命令后状态为 `idle`。`chat` 和 `gateway` 开始监听后显示 `idle`,接收的消息全部处理完毕后才显示完成。其他终端正常显示命令输出,重定向输出不包含状态控制序列。 +支持 [OSC 7501](https://www.superlogical.com/rex/docs/app/program-status) 的终端可以显示 Bub 的运行、等待输入或授权、完成和失败状态。取消命令后状态为 `idle`。`chat` 和 `gateway` 开始监听后显示 `idle`,接收的消息全部处理完毕后才显示完成。其他终端正常显示命令输出,重定向输出不包含状态控制序列。 ## `bub run` @@ -92,7 +92,7 @@ bub gateway [OPTIONS] - `--uninstall` 会停止并删除对应的 systemd user unit 或 Windows 计划任务。服务已经不存在时仍视为成功,且不要求当前存在已启用的渠道。 - `--install` 与 `--uninstall` 不能同时使用。 -部署细节见 [运维 › Channels](/zh-cn/docs/operate/channels/)。 +部署细节见 [运维 › Channels](/zh-cn/docs/app/gateway/)。 ## `bub onboard` diff --git a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx index 329c345d0..abd0d517b 100644 --- a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx @@ -3,7 +3,7 @@ title: 应用 Hooks description: Bub 插件的 hook 签名、调用规则和资源生命周期。 --- -Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 个 hooks。用法示例见[改变应用行为](/zh-cn/docs/build/hooks/),应用阶段顺序见[应用的一轮处理](/zh-cn/docs/concepts/turn-pipeline/)。 +Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 个 hooks。用法示例见[改变应用行为](/zh-cn/docs/app/hooks/),应用阶段顺序见[应用的一轮处理](/zh-cn/docs/concepts/turn/)。 下文的“首个”指按 hook 优先级取得第一个非 `None` 的结果,“全部”指调用每个实现。普通 hook 抛出异常时,本次分发停止,异常向上传播;[agent 拦截 hooks](#agent-loop-hooks-pluginagenthooks) 和 `on_error` 有各自的异常处理规则。 @@ -89,7 +89,7 @@ Bub 先注册内置 hooks,再注册已安装插件的入口点。普通实现 内置 lifespan 管理在作用域中启动的后台 shell 进程。POSIX 上关闭进程组,Windows 当前关闭直接 shell 进程。脱离进程组的进程,以及应用被强制终止的情况,需要宿主层面的进程管理。 -Tape sidecar providers 提供具名的关联 tape。Bub 把它们挂载到应用的根 Tape 上,fork、merge、archive 和 reset 都会处理已挂载的 sidecars。移除 provider 后,已保存的数据仍然保留。公共存储语义见 [Tape 与上下文](/zh-cn/docs/concepts/tape-and-context/)。 +Tape sidecar providers 提供具名的关联 tape。Bub 把它们挂载到应用的根 Tape 上,fork、merge、archive 和 reset 都会处理已挂载的 sidecars。移除 provider 后,已保存的数据仍然保留。公共存储语义见 [Tape 与上下文](/zh-cn/docs/concepts/tape/)。 diff --git a/website/src/content/docs/zh-cn/docs/reference/index.mdx b/website/src/content/docs/zh-cn/docs/reference/index.mdx index 2a4c101ce..933f67af0 100644 --- a/website/src/content/docs/zh-cn/docs/reference/index.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/index.mdx @@ -11,11 +11,11 @@ description: 查阅 Agent 接口、共享能力契约和 Bub 应用 API。 | 接口 | 参考 | | --- | --- | -| Agent 构造与模型客户端 | [Agent 参数](/zh-cn/docs/build/sdk/#agent-parameters)与[模型](/zh-cn/docs/build/sdk/#models) | -| 运行与流的生命周期 | [流与生命周期](/zh-cn/docs/build/sdk/#streams-and-lifecycle) | -| 提示词片段与循环定制 | [系统提示词](/zh-cn/docs/build/sdk/#system-prompt)与[定制循环](/zh-cn/docs/build/sdk/#customize-the-loop) | -| 工具与 CodeMode | [工具](/zh-cn/docs/build/tools/) | -| Tape、存储与上下文 | [Tape 与上下文](/zh-cn/docs/concepts/tape-and-context/) | +| Agent 构造与模型客户端 | [Agent 参数](/zh-cn/docs/sdk/#agent-parameters)与[模型](/zh-cn/docs/sdk/#models) | +| 运行与流的生命周期 | [流与生命周期](/zh-cn/docs/sdk/#streams-and-lifecycle) | +| 提示词片段与循环定制 | [系统提示词](/zh-cn/docs/sdk/#system-prompt)与[定制循环](/zh-cn/docs/sdk/#customize-the-loop) | +| 工具与 CodeMode | [工具](/zh-cn/docs/sdk/tools/) | +| Tape、存储与上下文 | [Tape 与上下文](/zh-cn/docs/concepts/tape/) | | 事件分发与执行决策 | [Agent 事件](/zh-cn/docs/concepts/events/) | diff --git a/website/src/content/docs/zh-cn/docs/reference/settings.mdx b/website/src/content/docs/zh-cn/docs/reference/settings.mdx index 5098c3538..1a631a389 100644 --- a/website/src/content/docs/zh-cn/docs/reference/settings.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/settings.mdx @@ -3,7 +3,7 @@ title: 应用设置 description: 应用内置环境变量、配置字段和默认值。 --- -这些设置用于配置 Bub 应用。表格列出内置的 `BUB_*` 变量及其 YAML 字段,插件设置由各插件文档说明。操作步骤见[配置应用](/zh-cn/docs/operate/configure/)。直接构造的 `core.Agent` 通过 Python 参数接收组件,不加载这些配置。 +这些设置用于配置 Bub 应用。表格列出内置的 `BUB_*` 变量及其 YAML 字段,插件设置由各插件文档说明。操作步骤见[配置应用](/zh-cn/docs/app/configure/)。直接构造的 `core.Agent` 通过 Python 参数接收组件,不加载这些配置。 ## 优先级 @@ -177,7 +177,7 @@ class TelegramSettings(Settings): | `BUB_TELEGRAM_ALLOW_CHATS` | 未设置 | `allow_chats` | 逗号分隔的允许 Telegram 聊天 ID 列表。留空表示不限制。 | | `BUB_TELEGRAM_PROXY` | 未设置 | `proxy` | 访问 Telegram API 的代理 URL,例如 `http://user:pass@host:port` 或 `socks5://host:port`。 | -部署细节见 [运维 › Channels › Telegram](/zh-cn/docs/operate/channels/telegram/)。 +部署细节见 [运维 › Channels › Telegram](/zh-cn/docs/app/telegram/)。 ## Login:第三方环境变量 @@ -189,4 +189,4 @@ class TelegramSettings(Settings): ## 插件自定义配置 -插件可以通过 `@config(name="...")` 装饰器注册自己的 `Settings` 子类(参见 [构建 › 插件](/zh-cn/docs/build/plugins/))。装饰器把类记录进 `CONFIG_MAP[name]`,然后由 `configure.validate` 验证、`ensure_config` 读取。YAML key 与注册名一致;环境变量遵循子类声明的 `env_prefix`。 +插件可以通过 `@config(name="...")` 装饰器注册自己的 `Settings` 子类(参见 [构建 › 插件](/zh-cn/docs/app/plugins/))。装饰器把类记录进 `CONFIG_MAP[name]`,然后由 `configure.validate` 验证、`ensure_config` 读取。YAML key 与注册名一致;环境变量遵循子类声明的 `env_prefix`。 diff --git a/website/src/content/docs/zh-cn/docs/reference/types.mdx b/website/src/content/docs/zh-cn/docs/reference/types.mdx index aa3b3f617..d19bfbf51 100644 --- a/website/src/content/docs/zh-cn/docs/reference/types.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/types.mdx @@ -3,7 +3,7 @@ title: 应用类型与生命周期 description: Bub 应用使用的消息、轮次、流和渠道接口。 --- -编写插件、接入渠道或嵌入 Bub 应用时,使用这里的类型。公共 agent 能力分别见[工具](/zh-cn/docs/build/tools/)、[Tape 与上下文](/zh-cn/docs/concepts/tape-and-context/)和[事件](/zh-cn/docs/concepts/events/)。 +编写插件、接入渠道或嵌入 Bub 应用时,使用这里的类型。公共 agent 能力分别见[工具](/zh-cn/docs/sdk/tools/)、[Tape 与上下文](/zh-cn/docs/concepts/tape/)和[事件](/zh-cn/docs/concepts/events/)。 ## 消息与轮次结果 @@ -135,7 +135,7 @@ from bub.builtin.agent import BubAgent 应用配置是进程级共享的。需要使用 framework 当前存储时,在 `framework.running()` 内创建 `BubAgent`。显式提供的 store 优先,生命周期仍由调用方管理。两种 store 都没有时,agent 使用内存存储。 -省略 `state` 时,`run_stream` 加载初始状态;传入 `state` 时,则直接使用调用方提供的状态。状态保存 hooks、出站消息处理和并发轮次调度由宿主管理。完整用法见[嵌入应用](/zh-cn/docs/build/distribution/#programmatic-embedding)。 +省略 `state` 时,`run_stream` 加载初始状态;传入 `state` 时,则直接使用调用方提供的状态。状态保存 hooks、出站消息处理和并发轮次调度由宿主管理。完整用法见[嵌入应用](/zh-cn/docs/app/package/#programmatic-embedding)。 ## 导入路径 diff --git a/website/src/content/docs/zh-cn/docs/reference/versions.mdx b/website/src/content/docs/zh-cn/docs/reference/versions.mdx index 160dbee9a..fd1ae809b 100644 --- a/website/src/content/docs/zh-cn/docs/reference/versions.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/versions.mdx @@ -21,13 +21,13 @@ uv pip install --upgrade "bub[app]" 插件或发行版的 `pyproject.toml` 也应把普通 `bub` 依赖改为 `bub[app]>=0.6.0`。这个 extra 安装内置运行时、Framework、配置、插件 hooks、skills 和渠道所需的依赖。 -运行 `bub --help`,再[完成一轮对话](/zh-cn/docs/getting-started/first-turn/)。检查插件是否加载时,使用 `bub hooks`。 +运行 `bub --help`,再[完成一轮对话](/zh-cn/docs/app/first-session/)。检查插件是否加载时,使用 `bub hooks`。 ## 按代码用途选择接口 | 代码用途 | 依赖与指南 | | --- | --- | -| 直接向 `core.Agent` 传入模型和工具 | `bub`;[构建自己的 Agent](/zh-cn/docs/build/sdk/) | -| 使用 `BubAgent`、Framework、应用 hooks 或渠道 | `bub[app]`;[定制 Bub 应用](/zh-cn/docs/build/) | +| 直接向 `core.Agent` 传入模型和工具 | `bub`;[构建自己的 Agent](/zh-cn/docs/sdk/) | +| 使用 `BubAgent`、Framework、应用 hooks 或渠道 | `bub[app]`;[定制 Bub 应用](/zh-cn/docs/app/) | -直接组合的 Agent 在 Python 中接收模型与组件。Bub 应用通过 `BubAgent` 和 Framework 提供配置、skills 与工具策略。[工具](/zh-cn/docs/build/tools/)和 [Agent 事件](/zh-cn/docs/concepts/events/)说明共同契约与默认行为的区别。 +直接组合的 Agent 在 Python 中接收模型与组件。Bub 应用通过 `BubAgent` 和 Framework 提供配置、skills 与工具策略。[工具](/zh-cn/docs/sdk/tools/)和 [Agent 事件](/zh-cn/docs/concepts/events/)说明共同契约与默认行为的区别。 diff --git a/website/src/content/docs/zh-cn/docs/build/reuse-plugins.mdx b/website/src/content/docs/zh-cn/docs/sdk/app-plugins.mdx similarity index 95% rename from website/src/content/docs/zh-cn/docs/build/reuse-plugins.mdx rename to website/src/content/docs/zh-cn/docs/sdk/app-plugins.mdx index 8c6c9a654..48468cfe0 100644 --- a/website/src/content/docs/zh-cn/docs/build/reuse-plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/app-plugins.mdx @@ -66,7 +66,7 @@ if __name__ == "__main__": 运行 `uv run python search_agent.py`。插件负责搜索请求及其 HTTP 连接;Agent 将会话记录到 `sessions/`,你的代码负责消费回复。 -如果已有 Agent,在启动时执行 `agent.tools[search.name] = search` 即可加入返回的工具。它的名称是 `web.search`,模型看到的名称是 `web_search`。其余调用代码可以沿用,包括现有的 [FastAPI 接口](/zh-cn/docs/tutorials/fastapi/)。 +如果已有 Agent,在启动时执行 `agent.tools[search.name] = search` 即可加入返回的工具。它的名称是 `web.search`,模型看到的名称是 `web_search`。其余调用代码可以沿用,包括现有的 [FastAPI 接口](/zh-cn/docs/sdk/web-service/)。 `register_tools()` 还会更新进程内的全局工具注册表,请在服务启动时完成这些准备。Agent 使用显式传入或加入的工具;本例取用返回的搜索工具,包注册的其他工具(如 Jina 的 `web.read`)可按需另行加入。 @@ -101,7 +101,7 @@ def add_context_budget(agent: Agent) -> None: 通过 `BUB_CONTEXT_BUDGET_MAX_CONTEXT_TOKENS`,或 `bub.yml` 中的 `context-budget.max_context_tokens` 设置上下文上限。插件从该上限中预留 16,384 tokens,再决定何时请求 handoff。这是一条给模型的预算提醒,实际执行取决于模型是否遵循指令。 -Handoff 把摘要保存在锚点上。下一次模型请求需要这份摘要时,通过[上下文选择](/zh-cn/docs/concepts/tape-and-context/#context)或下一条提示词将它带入。 +Handoff 把摘要保存在锚点上。下一次模型请求需要这份摘要时,通过[上下文选择](/zh-cn/docs/concepts/tape/#context)或下一条提示词将它带入。 注册和接入在服务启动时各执行一次,这个插件通过调用 hooks 完成工作。插件注册属于 Framework,`attach()` 将其 agent 循环 hooks 接到选定 Agent 的事件上;使用该 Agent 的会话共用这些 hooks。需要不同插件集合时,分别创建 Framework,配置仍是进程级的。 @@ -147,7 +147,7 @@ async with plugins.running(): | --- | --- | | 工具或存储 | 导入公开组件并传给 Agent。例如,`bub_tapestore_sqlalchemy.store.SQLAlchemyTapeStore` 接收数据库 URL,可作为 `store=` 传入。 | | 模型或工具拦截、系统提示词、继续运行或 steering | 注册指定插件并[接入 `AgentPlugins`](#agent-plugins),同时核对插件需要的状态字段和工具。 | -| 消息准备、模型阶段替换、状态保存或消息发送 | 在宿主中[嵌入应用流程](/zh-cn/docs/build/distribution/#programmatic-embedding),执行这些阶段并取得 `TurnResult`。 | +| 消息准备、模型阶段替换、状态保存或消息发送 | 在宿主中[嵌入应用流程](/zh-cn/docs/app/package/#programmatic-embedding),执行这些阶段并取得 `TurnResult`。 | | 后台连接或动态发现的工具 | 启动服务、等待就绪、绑定工具,并随宿主关闭。具体顺序遵循插件自己的生命周期接口。 | 按所选存储的生命周期约定管理资源。`framework.running()` 会获取 hooks 提供的资源,并在退出时关闭它进入的上下文管理器。返回普通对象的存储仍需遵循自己的清理约定。 @@ -168,6 +168,6 @@ async with plugins.running(): ## 继续构建 -- [工具与 CodeMode](/zh-cn/docs/build/tools/):Agent 如何使用你提供的工具。 -- [Tape 与上下文](/zh-cn/docs/concepts/tape-and-context/):存储与 handoff。 -- [打包插件](/zh-cn/docs/build/plugins/):将自己的扩展分享给其他人。 +- [工具与 CodeMode](/zh-cn/docs/sdk/tools/):Agent 如何使用你提供的工具。 +- [Tape 与上下文](/zh-cn/docs/concepts/tape/):存储与 handoff。 +- [打包插件](/zh-cn/docs/app/plugins/):将自己的扩展分享给其他人。 diff --git a/website/src/content/docs/zh-cn/docs/build/sdk.md b/website/src/content/docs/zh-cn/docs/sdk/index.md similarity index 93% rename from website/src/content/docs/zh-cn/docs/build/sdk.md rename to website/src/content/docs/zh-cn/docs/sdk/index.md index 4b464e2a9..41a164428 100644 --- a/website/src/content/docs/zh-cn/docs/build/sdk.md +++ b/website/src/content/docs/zh-cn/docs/sdk/index.md @@ -7,7 +7,7 @@ sidebar: 选择模型,加入工具,把 Agent 接到你的程序里。`bub.core.Agent` 负责模型与工具之间的循环;你决定如何保存会话,以及它怎样与你的系统协作。 -Agent 的组件由你直接提供。[Bub 应用](/zh-cn/docs/build/)建立在同一个内核上,提供配置和插件发现能力。你也可以在自有服务中[复用应用插件](/zh-cn/docs/build/reuse-plugins/),保留现有入口和调用代码。 +Agent 的组件由你直接提供。[Bub 应用](/zh-cn/docs/app/)建立在同一个内核上,提供配置和插件发现能力。你也可以在自有服务中[复用应用插件](/zh-cn/docs/sdk/app-plugins/),保留现有入口和调用代码。 ## 安装 @@ -84,7 +84,7 @@ uv run python sdk_example.py `agent.run()` 返回一个流,迭代这个流会推进执行:调用模型、执行模型请求的工具,再次调用模型,直到模型回答且不再请求工具。一个 `final` 事件表示一个模型步骤结束;完整消费流,才能完成这一轮运行。 -用 `async with` 管理流。无论正常结束、抛出异常,还是提前 `break`,退出代码块都会关闭流、释放打开的模型流,并合并本轮会话记录。运行失败也会保留已经写入的记录,详见[分支与部分结果](/zh-cn/docs/concepts/tape-and-context/#forks)。 +用 `async with` 管理流。无论正常结束、抛出异常,还是提前 `break`,退出代码块都会关闭流、释放打开的模型流,并合并本轮会话记录。运行失败也会保留已经写入的记录,详见[分支与部分结果](/zh-cn/docs/concepts/tape/#forks)。 如果另一个组件负责消费输出,可以用参数相同的 `await agent.start(...)` 取得流。接收它的组件必须负责关闭,包括尚未开始迭代的情况。自己消费输出时,优先使用 `run()`。 @@ -142,7 +142,7 @@ def customer(context: PromptContext) -> str | None: -传给运行的 `state` 是可变字典,由工具、提示词片段和事件处理器共用。哪些值需要保存、下次运行时如何恢复,由你决定,详见[本轮状态](/zh-cn/docs/concepts/tape-and-context/#state)。 +传给运行的 `state` 是可变字典,由工具、提示词片段和事件处理器共用。哪些值需要保存、下次运行时如何恢复,由你决定,详见[本轮状态](/zh-cn/docs/concepts/tape/#state)。 @@ -202,6 +202,6 @@ def check_results(event: StepFinished) -> None: ## 继续构建 -- [添加工具与 CodeMode](/zh-cn/docs/build/tools/),让 Agent 执行动作。 -- [保存会话历史](/zh-cn/docs/concepts/tape-and-context/),在多轮运行之间继续工作。 -- [通过 FastAPI 提供服务](/zh-cn/docs/tutorials/fastapi/),让其他程序通过 HTTP 调用 Agent。 +- [添加工具与 CodeMode](/zh-cn/docs/sdk/tools/),让 Agent 执行动作。 +- [保存会话历史](/zh-cn/docs/concepts/tape/),在多轮运行之间继续工作。 +- [通过 FastAPI 提供服务](/zh-cn/docs/sdk/web-service/),让其他程序通过 HTTP 调用 Agent。 diff --git a/website/src/content/docs/zh-cn/docs/build/tools.mdx b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx similarity index 91% rename from website/src/content/docs/zh-cn/docs/build/tools.mdx rename to website/src/content/docs/zh-cn/docs/sdk/tools.mdx index 4f2ff9914..af0c20e78 100644 --- a/website/src/content/docs/zh-cn/docs/build/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx @@ -5,9 +5,9 @@ sidebar: order: 3 --- -工具让 Agent 执行动作:查询订单、读取文件,或调用一个服务。`Tool` 描述动作的名称、参数和结果。你可以把它直接传给 Agent,也可以在 [Bub 应用](/zh-cn/docs/build/app-tools/)中注册。 +工具让 Agent 执行动作:查询订单、读取文件,或调用一个服务。`Tool` 描述动作的名称、参数和结果。你可以把它直接传给 Agent,也可以在 [Bub 应用](/zh-cn/docs/app/tools/)中注册。 -本页说明共同的工具接口和 SDK 接入方式。安装步骤与完整的模型循环示例见[构建自己的 Agent](/zh-cn/docs/build/sdk/)。 +本页说明共同的工具接口和 SDK 接入方式。安装步骤与完整的模型循环示例见[构建自己的 Agent](/zh-cn/docs/sdk/)。 @@ -31,7 +31,7 @@ agent = Agent(tools=[add_tool]) 运行时保留 `math.add` 这个名称;提供给模型时,点会换成下划线,因此模型看到的是 `math_add`。也可以通过 `description=` 显式提供描述。 -普通 `Agent` 会把传入的工具提供给模型,仅排除 `exposure="command"`。延迟加载、`allowed_tools` 状态和 `state["code_mode"]` 开关属于 [BubAgent 的应用策略](/zh-cn/docs/build/app-tools/)。在自己的 Agent 中按轮次选择工具,可以覆写 [`prepare_tools()`](/zh-cn/docs/build/sdk/#customize-the-loop)。 +普通 `Agent` 会把传入的工具提供给模型,仅排除 `exposure="command"`。延迟加载、`allowed_tools` 状态和 `state["code_mode"]` 开关属于 [BubAgent 的应用策略](/zh-cn/docs/app/tools/)。在自己的 Agent 中按轮次选择工具,可以覆写 [`prepare_tools()`](/zh-cn/docs/sdk/#customize-the-loop)。 @@ -82,7 +82,7 @@ async def remember(note: str, *, context: ToolContext) -> str: remember_tool = Tool.from_callable(remember, context=True) ``` -这会在 Tape 中写入一条业务事件。默认上下文选择器读取会话消息与工具交互;要把这条事件交给模型,需要提供自己的选择逻辑。哪些已保存的信息应交给模型,由[会话历史与上下文](/zh-cn/docs/concepts/tape-and-context/#context)中的选择规则决定。 +这会在 Tape 中写入一条业务事件。默认上下文选择器读取会话消息与工具交互;要把这条事件交给模型,需要提供自己的选择逻辑。哪些已保存的信息应交给模型,由[会话历史与上下文](/zh-cn/docs/concepts/tape/#context)中的选择规则决定。 @@ -103,7 +103,7 @@ agent = Agent( ) ``` -按 [SDK 指南](/zh-cn/docs/build/sdk/#models)中的方式管理模型并运行 Agent。模型可以向 `run_code` 传入这样的代码: +按 [SDK 指南](/zh-cn/docs/sdk/#models)中的方式管理模型并运行 Agent。模型可以向 `run_code` 传入这样的代码: ```python order = await tools.lookup_order(order_id="A123") @@ -112,7 +112,7 @@ print(order["status"]) 在 `run_code` 中,工具名称使用下划线,调用使用关键字参数,结果保留结构化形式。代码支持顶层 `await`。工具调用经过 `ToolCallStarted` 和 `ToolCallFinished`,其中 `context.code_mode=True`。 -`CodeMode` 使用你传入的工具,并排除 `exposure="command"`。BubAgent 通过[应用代码模式设置](/zh-cn/docs/build/app-tools/)对 `auto`、`direct`、`code` 工具分组。 +`CodeMode` 使用你传入的工具,并排除 `exposure="command"`。BubAgent 通过[应用代码模式设置](/zh-cn/docs/app/tools/)对 `auto`、`direct`、`code` 工具分组。 | 选项 | 行为 | | --- | --- | @@ -165,4 +165,4 @@ asyncio.run(calculate()) 它会打印 `5`。命令直接调用传入的工具,发出工具事件和 `CommandFinished`,不进入模型循环。前缀不能为空,也不能包含空白字符。参数可以先按位置传入,再使用 `key=value`。如果命令不是工具名称,而 Agent 提供了 `bash` 工具,就会交给它执行。 -[Bub 应用](/zh-cn/docs/build/app-tools/)提供 `,skill`、`,code_mode` 等命令。 +[Bub 应用](/zh-cn/docs/app/tools/)提供 `,skill`、`,code_mode` 等命令。 diff --git a/website/src/content/docs/zh-cn/docs/tutorials/fastapi.mdx b/website/src/content/docs/zh-cn/docs/sdk/web-service.mdx similarity index 86% rename from website/src/content/docs/zh-cn/docs/tutorials/fastapi.mdx rename to website/src/content/docs/zh-cn/docs/sdk/web-service.mdx index 6c232e317..8dbd1fceb 100644 --- a/website/src/content/docs/zh-cn/docs/tutorials/fastapi.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/web-service.mdx @@ -7,7 +7,7 @@ sidebar: 为 Agent 提供 HTTP 接口,让其他程序可以向它提交任务。每个请求指定会话名称;使用相同名称的请求会继续磁盘上保存的会话。 -本指南接续[构建自己的 Agent](/zh-cn/docs/build/sdk/),直接使用 `core.Agent`。FastAPI 管理模型的生命周期,每个请求负责管理自己的 Agent 输出流。 +本指南接续[构建自己的 Agent](/zh-cn/docs/sdk/),直接使用 `core.Agent`。FastAPI 管理模型的生命周期,每个请求负责管理自己的 Agent 输出流。 ## 安装 @@ -21,7 +21,7 @@ uv add bub fastapi uvicorn 这个服务使用基础 SDK 依赖,以及 FastAPI 和 Uvicorn。 -如果要接入已有的 Bub 插件,见[复用应用插件](/zh-cn/docs/build/reuse-plugins/)。加入插件工具或指定 hooks 后,仍可沿用下面的接口与请求生命周期。 +如果要接入已有的 Bub 插件,见[复用应用插件](/zh-cn/docs/sdk/app-plugins/)。加入插件工具或指定 hooks 后,仍可沿用下面的接口与请求生命周期。 ## 编写服务 @@ -122,8 +122,8 @@ curl -s localhost:8000/tasks -H 'content-type: application/json' \ 示例为每个会话保留一个内存锁。长期创建大量会话的服务应清理空闲锁。这些锁只在单进程内生效;多个 worker 共享 Tape 存储时,需要跨进程协调。协调范围应覆盖整轮运行,包括模型调用和 Tape 写入。 -`FileTapeStore` 将历史保存在一台主机上。使用其他后端时,实现 `TapeStore` 或 `AsyncTapeStore`,再传给 `Agent(store=...)`,详见[选择历史存储位置](/zh-cn/docs/concepts/tape-and-context/#storage)。如果存储连接需要打开和关闭,也应纳入应用的 lifespan。 +`FileTapeStore` 将历史保存在一台主机上。使用其他后端时,实现 `TapeStore` 或 `AsyncTapeStore`,再传给 `Agent(store=...)`,详见[选择历史存储位置](/zh-cn/docs/concepts/tape/#storage)。如果存储连接需要打开和关闭,也应纳入应用的 lifespan。 ## 扩展服务 -通过[工具或 CodeMode](/zh-cn/docs/build/tools/)增加动作,用[事件处理器](/zh-cn/docs/concepts/events/)观察调用或在执行前作出决定,通过 [TapeContext](/zh-cn/docs/concepts/tape-and-context/#context)选择交给模型的内容。 +通过[工具或 CodeMode](/zh-cn/docs/sdk/tools/)增加动作,用[事件处理器](/zh-cn/docs/concepts/events/)观察调用或在执行前作出决定,通过 [TapeContext](/zh-cn/docs/concepts/tape/#context)选择交给模型的内容。 diff --git a/website/src/content/docs/zh-cn/docs/tutorials/index.mdx b/website/src/content/docs/zh-cn/docs/tutorials/index.mdx deleted file mode 100644 index e85621d22..000000000 --- a/website/src/content/docs/zh-cn/docs/tutorials/index.mdx +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: 把 Bub 接入你的工具 -description: 接入 HTTP 服务、编辑器、模型服务或应用扩展。 -sidebar: - order: 0 ---- - -按你使用 Bub 的方式选择接入指南。每篇指南会说明所需组件,并给出检查结果的方法。 - -## 构建自己的 Agent - -- [用 FastAPI 提供 Agent 服务](/zh-cn/docs/tutorials/fastapi/):把 `core.Agent` 接入 HTTP 服务,由服务管理会话存储。 - -## 定制 Bub 应用 - -这些指南使用应用的 CLI、配置或插件机制。先[安装 Bub 应用](/zh-cn/docs/getting-started/install/),把 Bub 和插件放在同一个环境中。 - -- [连接 MCP 服务器](/zh-cn/docs/tutorials/mcp/):调用 MCP 服务器提供的工具。 -- [加载 Agent Plugins](/zh-cn/docs/tutorials/agent-plugins/):加载包含 skills 和 MCP 服务器的可移植插件包。 -- [接入 ACP 编辑器](/zh-cn/docs/tutorials/acp-server/):在 Zed 等编辑器中使用 Bub。 -- [使用本地 llama.cpp 模型](/zh-cn/docs/tutorials/local-llama-cpp/):让应用调用本地模型服务。 -- [把会话历史存入 SQLite](/zh-cn/docs/tutorials/tapestore-sqlalchemy/):通过插件替换应用的文件存储。 -- [检查会话记录和执行轨迹](/zh-cn/docs/tutorials/observability/):查看本地记录,或将轨迹导出到 Phoenix 和 Logfire。 -- [用其他语言实现 hooks](/zh-cn/docs/tutorials/language-extensions/):尝试实验性的 WebAssembly 接入。 - -各集成指南都提供插件文档链接,可据此查看该插件的具体配置方式。 From cd211b25f9941349e3547383dd25e5c295f6eaae Mon Sep 17 00:00:00 2001 From: 100gle Date: Wed, 21 Oct 2026 12:09:24 +0800 Subject: [PATCH 02/28] docs(get-started): add an introduction and fix the entry order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The group used to open on a choice between two paths, and then on the install page. It now reads: what Bub is, how to install it, how to run it once. - add "What is Bub?" as the first page: the loop, the two layers, the two ways to use it, three design rules, and an index of the rest of the documentation - move the old index page to quickstart/ so the group is linear instead of a hub - show the application install before the library install - redirect /docs to the introduction - rename the group label to "开始" in Chinese --- README.md | 4 +- website/astro.config.mjs | 4 +- .../docs/docs/getting-started/index.mdx | 66 ++++++++++--- .../docs/docs/getting-started/install.mdx | 30 +++--- .../docs/docs/getting-started/quickstart.mdx | 99 +++++++++++++++++++ .../docs/zh-cn/docs/getting-started/index.mdx | 66 ++++++++++--- .../zh-cn/docs/getting-started/install.mdx | 30 +++--- .../zh-cn/docs/getting-started/quickstart.mdx | 99 +++++++++++++++++++ 8 files changed, 340 insertions(+), 58 deletions(-) create mode 100644 website/src/content/docs/docs/getting-started/quickstart.mdx create mode 100644 website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx diff --git a/README.md b/README.md index ff3b856ea..3534c4ad6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

Build your Bub.

-[Website](https://bub.build) · [Docs](https://bub.build/docs/getting-started/) · [Hub](https://hub.bub.build) +[Website](https://bub.build) · [Docs](https://bub.build/docs/) · [Hub](https://hub.bub.build) @@ -55,7 +55,7 @@ bub chat ## Documentation -- [Choose a starting point](https://bub.build/docs/getting-started/) +- [Choose a starting point](https://bub.build/docs/) - [Shared concepts and capabilities](https://bub.build/docs/concepts/) - [API and application reference](https://bub.build/docs/reference/) diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 2be6e74c0..5d157b8b7 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -147,8 +147,8 @@ export default defineConfig({ sidebar: [ { label: 'Get started', - translations: { 'zh-CN': '快速开始' }, - items: ['docs/getting-started', 'docs/getting-started/install'], + translations: { 'zh-CN': '开始' }, + items: ['docs/getting-started', 'docs/getting-started/install', 'docs/getting-started/quickstart'], }, { label: 'Concepts', diff --git a/website/src/content/docs/docs/getting-started/index.mdx b/website/src/content/docs/docs/getting-started/index.mdx index 89875b86c..058b357e4 100644 --- a/website/src/content/docs/docs/getting-started/index.mdx +++ b/website/src/content/docs/docs/getting-started/index.mdx @@ -1,26 +1,68 @@ --- -title: Build your Bub -description: Put an agent in your own Python program, or customize the Bub application. +title: What is Bub? +description: An agent runtime written in Python. Run it as an application, or import it in your own program. sidebar: order: 0 --- -Put an agent in your own Python program, or start with the Bub application and customize it. The application uses the same agent kernel and adds configuration, plugins, and channel integration. +import { CardGrid, LinkCard } from '@astrojs/starlight/components'; -## Use the Bub app +Bub is an agent runtime written in Python. It needs Python 3.12 or newer and installs from PyPI with `pip` or `uv`. -Run Bub, then use configuration and plugins to add the behavior you need. This route also covers skills, channels, and deployment. +You give Bub three things: a model, a set of tools, and a place to keep the conversation. Then it repeats one loop. It calls the model, runs the tools the model asks for, writes the results to the conversation, and calls the model again. The loop ends when the model answers without asking for a tool. -[Run your first conversation](/docs/app/first-session/) gets the app running. [Use the Bub app](/docs/app/) helps you choose what to extend next. +Everything else in Bub exists to make that loop usable outside a demo: settings, storage, channels, plugins, and a way to watch what a run is doing. -## Build your own Bub Agent +## What it contains -Choose a model and tools, assemble an agent with `bub.core.Agent`, and connect it to your program. Start by defining a tool that looks up an order, then let the agent call it. +Bub has two layers. -[Build your own Bub Agent](/docs/sdk/) takes you through the Python example. When you need an HTTP endpoint, continue with [FastAPI](/docs/sdk/web-service/). +**The kernel** (`bub.core`) has the loop, the tool interface, the conversation record, the event bus, and the model client. It uses `republic` for model calls, and depends on `pydantic` and `loguru`. -## Read the common explanations as you need them +**The application layer** (`bub[app]`) adds what turns the kernel into a program you can run: settings and config files, plugins and hooks, skills, message channels, a gateway, and the `bub` command. -Tools, conversation history, and events share one set of [explanations](/docs/concepts/). Each route gives you its own setup steps and default behavior. +`pip install bub` installs the kernel. `pip install "bub[app]"` installs both layers. -[Install Bub](/docs/getting-started/install/) for your chosen route, then run its first example. +## Two ways to use it + +| | Use Bub App | Build your own Bub Agent | +| --- | --- | --- | +| What runs | the `bub` command, in its own process | your Python program | +| How you start it | `bub chat`, `bub run`, `bub gateway` | `Agent(...)` | +| What you provide | model and channel settings | a model, tools, and a tape | +| What Bub provides | settings, plugins, skills, hooks, channels | the loop and its events | + +Use the app when you want an agent that already works in a terminal or a chat service. Build your own agent when you are adding agent behavior to a service you run yourself. + +The two are not exclusive. A program that imports `BubAgent` gets the kernel plus the app's settings and plugin hooks, without running the `bub` command. + +## Core design + +A few rules keep the loop small: + +- History is append-only. Each turn is written to a tape. The model reads a slice of that tape, not a chat array that grows without limit. +- Every step reports an event. Model calls and tool calls each report a start, an end, and a result. A host can watch a run, change the next input, or stop it. +- A tool is a function. A Python function with type hints is enough. Code mode lets the model write a short program that calls several tools in one step. + +## Where to go next + + + + + + +The rest of the documentation, in reading order: + +- **[Concepts](/docs/concepts/)**: tape, turns, events, extension points. Read these when a term in the guides is unclear; nothing else depends on them. +- **[Use the Bub app](/docs/app/)**: configure it, then add tools, skills, plugins, hooks, and channels. +- **[Build your own agent](/docs/sdk/)**: keep the kernel and write the loop's surroundings yourself. +- **[Integrations](/docs/integrations/)**: MCP, ACP, agent plugins, local models, SQLite, and tracing. +- **[Reference](/docs/reference/)**: CLI commands, settings, types, hooks, and version history. diff --git a/website/src/content/docs/docs/getting-started/install.mdx b/website/src/content/docs/docs/getting-started/install.mdx index 6159d1a7f..6d275711f 100644 --- a/website/src/content/docs/docs/getting-started/install.mdx +++ b/website/src/content/docs/docs/getting-started/install.mdx @@ -9,21 +9,6 @@ import { Tabs, TabItem } from '@astrojs/starlight/components'; Choose the installation for the way you want to use Bub. Add Bub to a Python project with [uv](https://docs.astral.sh/uv/getting-started/installation/), or install the application with a setup script or uv. - - -## Build your own agent - -Use Python 3.12 or later and add Bub to your project: - -```bash -uv add bub -uv run python -c "from bub.core import Agent; print(Agent.__name__)" -``` - -If you are starting a new project, first run `uv init --python 3.12 my-agent` and `cd my-agent`. - -The base installation includes the kernel's dependencies: Republic, Pydantic, Loguru, and typing-extensions. The import check needs no model credentials. Continue with [Build your own agent](/docs/sdk/) to configure a model in Python and run the first example. - ## Run the Bub application @@ -69,6 +54,21 @@ Continue with [Run Bub for the first time](/docs/app/first-session/) to start a The base install and `bub[app]` contain the same package files. The extra changes which dependencies are installed. Without the application dependencies, the `bub` command exits with an instruction to install `bub[app]`. + + +## Build your own agent + +Use Python 3.12 or later and add Bub to your project: + +```bash +uv add bub +uv run python -c "from bub.core import Agent; print(Agent.__name__)" +``` + +If you are starting a new project, first run `uv init --python 3.12 my-agent` and `cd my-agent`. + +The base installation includes the kernel's dependencies: Republic, Pydantic, Loguru, and typing-extensions. The import check needs no model credentials. Continue with [Build your own agent](/docs/sdk/) to configure a model in Python and run the first example. + ## Use a virtual environment for application development diff --git a/website/src/content/docs/docs/getting-started/quickstart.mdx b/website/src/content/docs/docs/getting-started/quickstart.mdx new file mode 100644 index 000000000..8d685379f --- /dev/null +++ b/website/src/content/docs/docs/getting-started/quickstart.mdx @@ -0,0 +1,99 @@ +--- +title: Quick start +description: Run Bub in your terminal, or embed it in a Python program. +sidebar: + order: 2 +--- + +import { CardGrid, LinkCard, Tabs, TabItem } from '@astrojs/starlight/components'; + +Bub runs in two places: as an application in its own process, or as a library inside your Python program. Both run the same kernel, so you can move between them later. + +This page assumes [Bub is installed](/docs/getting-started/install/). + +## Run it + + + + +Configure a model once, then start a conversation in any project directory: + +```bash +bub onboard +``` + +```bash +mkdir hello-bub && cd hello-bub +bub chat +``` + +Ask it something: + +```text +> Summarize the files in this directory +``` + +Bub reads `AGENTS.md` when the directory has one, records the turn on its tape, and answers with the model you configured. Press `Ctrl+C` to leave. + +Next: [your first session](/docs/app/first-session/) covers workspaces, commands, and session names. + + + + +Add Bub to a project and drive the loop yourself: + +```bash +uv init --python 3.12 my-agent && cd my-agent +uv add bub +``` + +Save this as `main.py`: + +```python +import asyncio + +import republic + +from bub.core import Agent + + +async def main() -> None: + agent = Agent(system_prompt="Be concise.") + async with republic.get_model("openai:gpt-5-mini") as model: + async with agent.run("What can you do?", model=model, tape="demo") as stream: + async for event in stream: + if event.kind == "text": + print(event.data.get("delta", ""), end="") + + +asyncio.run(main()) +``` + +```bash +export REPUBLIC_OPENAI_API_KEY=sk-... +uv run python main.py +``` + +`Agent` owns the model and tool loop; you pass the model, the tools, and the tape name. The stream reports `text` for the answer, plus `tool_call` and `tool_result` when the model calls a tool. + +Next: [Build your own Bub Agent](/docs/sdk/) adds a tool and explains the stream lifecycle. + + + + +## Where each path goes + + + + + + +Concepts are optional. Every page says when it is worth reading, so you can skip them and come back when a task needs them. diff --git a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx index 5a9c45653..f8dad900d 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx @@ -1,26 +1,68 @@ --- -title: 构建适合你工作的 Bub -description: 把 Agent 接入自己的 Python 程序,或从现成的 Bub 应用开始定制。 +title: Bub 是什么? +description: 一个用 Python 写的 agent 运行时。可以作为应用运行,也可以引入到你自己的程序里。 sidebar: order: 0 --- -把 Agent 接入自己的 Python 程序,或从现成的 Bub 应用开始定制。Bub 应用使用同一个 Agent 内核,并提供配置、插件和渠道接入。 +import { CardGrid, LinkCard } from '@astrojs/starlight/components'; -## 使用 Bub 应用 +Bub 是一个用 Python 写的 agent 运行时。它要求 Python 3.12 或更高版本,用 `pip` 或 `uv` 从 PyPI 安装。 -先运行 Bub,再通过配置和插件添加需要的行为。这条路线也包含 skills、渠道和部署。 +你给 Bub 三样东西:一个模型、一组工具、一个存放对话的地方。然后它重复同一个循环:调用模型,执行模型要求的工具,把结果写进对话,再调用模型。模型不再要求工具、直接给出回答时,循环结束。 -从[运行第一轮对话](/zh-cn/docs/app/first-session/)开始;应用跑通后,到[使用 Bub 应用](/zh-cn/docs/app/)选择下一步要扩展的部分。 +Bub 其余的部分,都是为了让这个循环在演示之外也能用:配置、存储、渠道、插件,以及看清一次运行在做什么的手段。 -## 构建你的 Bub Agent +## 它由什么组成 -选择模型和工具,用 `bub.core.Agent` 组合 Agent,再接入自己的程序。先定义一个查询订单状态的工具,让 Agent 调用一次。 +Bub 分两层。 -[构建你的 Bub Agent](/zh-cn/docs/sdk/)会带你跑通这个 Python 示例。需要 HTTP 接口时,继续阅读 [FastAPI 接入](/zh-cn/docs/sdk/web-service/)。 +**内核**(`bub.core`)有循环、工具接口、对话记录、事件总线和模型客户端。它用 `republic` 调用模型,依赖 `pydantic` 和 `loguru`。 -## 用到时,再查共同机制 +**应用层**(`bub[app]`)加上让内核变成一个能直接运行的程序所需的部分:配置和配置文件、插件与 hooks、skills、消息渠道、网关,以及 `bub` 命令。 -工具、会话历史和事件的工作方式共用一份[说明](/zh-cn/docs/concepts/)。各自的指南给出接入步骤和默认行为。 +`pip install bub` 只装内核。`pip install "bub[app]"` 两层都装。 -按所选路线[安装 Bub](/zh-cn/docs/getting-started/install/),然后运行第一个示例。 +## 两种用法 + +| | 使用 Bub 应用 | 构建你的 Bub Agent | +| --- | --- | --- | +| 运行的是什么 | `bub` 命令,独占一个进程 | 你自己的 Python 程序 | +| 怎么启动 | `bub chat`、`bub run`、`bub gateway` | `Agent(...)` | +| 你需要提供 | 模型和渠道配置 | 模型、工具、一个 tape | +| Bub 提供 | 配置、插件、skills、hooks、渠道 | 循环和它的事件 | + +想要一个在终端或聊天软件里就能用的 agent,用应用。想给自己跑的服务加上 agent 行为,就自己构建。 + +两者并不冲突。导入 `BubAgent` 的程序会拿到内核,外加应用的配置和插件 hooks,但不需要跑 `bub` 命令。 + +## 核心设计 + +几条约束让循环保持简单: + +- 历史只追加。每一轮写进 tape。模型读的是这份记录的一个切片,而不是一个无限增长的 chat 数组。 +- 每一步都发事件。模型调用和工具调用各自发出开始、结束和结果。宿主可以旁观一次运行,改掉下一次的输入,或者把它停下来。 +- 工具就是函数。带类型标注的 Python 函数就够了。code mode 让模型写一小段程序,一步里调用多个工具。 + +## 接下来 + + + + + + +其余文档,按阅读顺序: + +- **[核心概念](/zh-cn/docs/concepts/)**:tape、轮次、事件、扩展点。指南里遇到看不懂的词再回来读,后面的页面不依赖它。 +- **[使用 Bub 应用](/zh-cn/docs/app/)**:先配置,再加工具、skills、插件、hooks 和渠道。 +- **[构建你的 Bub Agent](/zh-cn/docs/sdk/)**:留下内核,循环周边自己写。 +- **[集成](/zh-cn/docs/integrations/)**:MCP、ACP、agent plugins、本地模型、SQLite、可观测性。 +- **[参考](/zh-cn/docs/reference/)**:CLI 命令、配置项、类型、hooks、版本记录。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx index 1ff7e61e0..7f38916a9 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx @@ -9,21 +9,6 @@ import { Tabs, TabItem } from '@astrojs/starlight/components'; 按你的使用方式选择安装路径。Python 项目通过 [uv](https://docs.astral.sh/uv/getting-started/installation/) 添加 Bub;运行应用可以使用安装脚本或 uv。 - - -## 构建自己的 Agent - -使用 Python 3.12 或更高版本,在项目中添加 Bub: - -```bash -uv add bub -uv run python -c "from bub.core import Agent; print(Agent.__name__)" -``` - -如果还没有项目,先运行 `uv init --python 3.12 my-agent`,再进入 `cd my-agent`。 - -基础安装包含内核所需的 Republic、Pydantic、Loguru 和 typing-extensions。导入检查无需模型凭据。接着阅读[构建自己的 Agent](/zh-cn/docs/sdk/),在 Python 中配置模型并运行第一个示例。 - ## 运行 Bub 应用 @@ -69,6 +54,21 @@ bub --help 基础安装与 `bub[app]` 包含相同的包文件;extra 改变的是安装的依赖。缺少应用依赖时,`bub` 命令会退出,并提示安装 `bub[app]`。 + + +## 构建自己的 Agent + +使用 Python 3.12 或更高版本,在项目中添加 Bub: + +```bash +uv add bub +uv run python -c "from bub.core import Agent; print(Agent.__name__)" +``` + +如果还没有项目,先运行 `uv init --python 3.12 my-agent`,再进入 `cd my-agent`。 + +基础安装包含内核所需的 Republic、Pydantic、Loguru 和 typing-extensions。导入检查无需模型凭据。接着阅读[构建自己的 Agent](/zh-cn/docs/sdk/),在 Python 中配置模型并运行第一个示例。 + ## 在虚拟环境中开发应用 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx b/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx new file mode 100644 index 000000000..57f7238ff --- /dev/null +++ b/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx @@ -0,0 +1,99 @@ +--- +title: 快速上手 +description: 在终端里运行 Bub,或把它嵌进 Python 程序。 +sidebar: + order: 2 +--- + +import { CardGrid, LinkCard, Tabs, TabItem } from '@astrojs/starlight/components'; + +Bub 有两种跑法:作为独立进程的应用,或作为 Python 程序里的库。两者跑的是同一套内核,之后可以随时换。 + +这一页假设你已经[安装 Bub](/zh-cn/docs/getting-started/install/)。 + +## 跑起来 + + + + +先配置一次模型,然后在任意项目目录里开始对话: + +```bash +bub onboard +``` + +```bash +mkdir hello-bub && cd hello-bub +bub chat +``` + +问它一个问题: + +```text +> Summarize the files in this directory +``` + +目录里有 `AGENTS.md` 时 Bub 会读取它,把这一轮记到 tape 上,再用你配置的模型回答。按 `Ctrl+C` 退出。 + +下一步:[第一次会话](/zh-cn/docs/app/first-session/)会讲工作区、命令和会话命名。 + + + + +在自己的项目里添加 Bub,然后自己驱动循环: + +```bash +uv init --python 3.12 my-agent && cd my-agent +uv add bub +``` + +把这段保存为 `main.py`: + +```python +import asyncio + +import republic + +from bub.core import Agent + + +async def main() -> None: + agent = Agent(system_prompt="Be concise.") + async with republic.get_model("openai:gpt-5-mini") as model: + async with agent.run("What can you do?", model=model, tape="demo") as stream: + async for event in stream: + if event.kind == "text": + print(event.data.get("delta", ""), end="") + + +asyncio.run(main()) +``` + +```bash +export REPUBLIC_OPENAI_API_KEY=sk-... +uv run python main.py +``` + +`Agent` 负责模型与工具循环;模型、工具和 tape 名字由你传入。事件流里 `text` 是回答,模型调用工具时还会有 `tool_call` 和 `tool_result`。 + +下一步:[构建你的 Bub Agent](/zh-cn/docs/sdk/)会加一个工具,并讲清事件流的生命周期。 + + + + +## 两条路各自通向哪里 + + + + + + +概念部分可以跳过。每页都会写明什么时候值得读,你可以在遇到具体问题时再回头。 From a2ee62f8fa022b02bc4edfc77d87962f90f5ebeb Mon Sep 17 00:00:00 2001 From: 100gle Date: Wed, 21 Oct 2026 13:07:00 +0800 Subject: [PATCH 03/28] docs(concepts): name the pages after the concepts The group used to hold six pages with titles that described the work ("Shared concepts and capabilities", "Where to add your code") instead of the thing itself. Readers could not tell from the sidebar what each page covered. - merge the overview and the design page: one page now covers the four concepts, the three layers, the terms, and the reasons behind them - name the remaining pages Turn, Tape, Events, and Extension points, matching bub.turn, bub.core.tape, bub.core.events, and the extension table - rename "Pipeline" to "Turn": process_inbound returns TurnResult, so a turn is the concept and the pipeline is only how the app handles one - state the turn/run/step distinction, which the pages used loosely - say why hooks are not a core concept: the kernel reads no plugin hooks - redirect /docs/concepts/design/ to the overview - rewrite the prose in short sentences, one instruction per sentence --- website/astro.config.mjs | 6 +- .../src/content/docs/docs/concepts/design.mdx | 32 ------- .../src/content/docs/docs/concepts/events.mdx | 84 ++++++++--------- .../docs/docs/concepts/extension-points.mdx | 32 +++---- .../src/content/docs/docs/concepts/index.mdx | 51 +++++++--- .../src/content/docs/docs/concepts/tape.mdx | 78 ++++++++-------- .../src/content/docs/docs/concepts/turn.mdx | 44 ++++----- .../content/docs/docs/integrations/acp.mdx | 2 +- .../src/content/docs/docs/reference/hooks.mdx | 2 +- .../src/content/docs/zh-cn/docs/app/index.mdx | 2 +- .../docs/zh-cn/docs/concepts/design.mdx | 32 ------- .../docs/zh-cn/docs/concepts/events.mdx | 92 +++++++++---------- .../zh-cn/docs/concepts/extension-points.mdx | 36 ++++---- .../docs/zh-cn/docs/concepts/index.mdx | 51 +++++++--- .../content/docs/zh-cn/docs/concepts/tape.mdx | 84 ++++++++--------- .../content/docs/zh-cn/docs/concepts/turn.mdx | 52 +++++------ .../docs/zh-cn/docs/integrations/acp.mdx | 2 +- .../zh-cn/docs/integrations/observability.mdx | 2 +- .../docs/zh-cn/docs/reference/hooks.mdx | 2 +- .../docs/zh-cn/docs/reference/index.mdx | 2 +- .../src/content/docs/zh-cn/docs/sdk/tools.mdx | 2 +- 21 files changed, 336 insertions(+), 354 deletions(-) delete mode 100644 website/src/content/docs/docs/concepts/design.mdx delete mode 100644 website/src/content/docs/zh-cn/docs/concepts/design.mdx diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 5d157b8b7..5a22cb455 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -42,7 +42,8 @@ export default defineConfig({ '/docs/build/hooks': '/docs/app/hooks/', '/docs/build/reuse-plugins': '/docs/sdk/app-plugins/', '/docs/build/distribution': '/docs/app/package/', - '/docs/concepts/philosophy': '/docs/concepts/design/', + '/docs/concepts/philosophy': '/docs/concepts/', + '/docs/concepts/design': '/docs/concepts/', '/docs/concepts/surfaces': '/docs/concepts/extension-points/', '/docs/concepts/tape-and-context': '/docs/concepts/tape/', '/docs/concepts/turn-pipeline': '/docs/concepts/turn/', @@ -155,11 +156,10 @@ export default defineConfig({ translations: { 'zh-CN': '核心概念' }, items: [ 'docs/concepts', - 'docs/concepts/tape', 'docs/concepts/turn', + 'docs/concepts/tape', 'docs/concepts/events', 'docs/concepts/extension-points', - 'docs/concepts/design', ], }, { diff --git a/website/src/content/docs/docs/concepts/design.mdx b/website/src/content/docs/docs/concepts/design.mdx deleted file mode 100644 index 45e3650ad..000000000 --- a/website/src/content/docs/docs/concepts/design.mdx +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Design choices -description: Why Bub separates the agent loop, application defaults, and conversation context. -sidebar: - order: 1 ---- - -Bub lets you choose how much of the application to use. You can connect the agent core to a program you already have, or customize the working Bub application. Both routes use the same model and tool loop. - -## Assemble what your program needs - -`core.Agent` accepts a model, tools, prompt sections, and a tape store. You provide these components directly, so a small Python service can use the core dependency set. - -`BubAgent` adds the application's defaults: settings, skills, commands, tool selection, and steering. `BubFramework` manages plugins, message processing, and outbound routing around it. Choose them when you want to reuse that behavior. - -The [installation guide](/docs/getting-started/install/) explains the dependency choices. The [SDK guide](/docs/sdk/) and [application guide](/docs/app/) show how to put them to work. - -## Keep history and context separate - -A conversation can be longer than the context needed for the next step. Tape keeps the recorded history; context selection decides what the model reads. An anchor starts a new context without deleting earlier work. - -That makes it possible to inspect saved messages and tool results while keeping the next request focused. Persistence still depends on the store you choose, and custom application state needs an explicit storage policy. See [Conversation history and context](/docs/concepts/tape/). - -## Make extension behavior visible - -A tool describes an action. A skill describes a procedure. A channel connects the application to a place where people work. Keeping these roles distinct helps you choose what to change. - -At runtime, events expose model and tool calls. Application hooks also cover configuration, resource setup, and message routing. Each has an ordering and error contract; the [extension guide](/docs/concepts/extension-points/) points to the appropriate one. - -## Background - -[Why we rewrote Bub](/posts/why-rewrite-bub/) and [Socialized evaluation and agent partnership](/posts/socialized-evaluation/) describe the project's earlier design reasoning. Use the current guides and references for today's interfaces and behavior. diff --git a/website/src/content/docs/docs/concepts/events.mdx b/website/src/content/docs/docs/concepts/events.mdx index 9f527fa8a..c8d20bd99 100644 --- a/website/src/content/docs/docs/concepts/events.mdx +++ b/website/src/content/docs/docs/concepts/events.mdx @@ -1,27 +1,27 @@ --- -title: Events and call control -description: Observe agent progress, intercept model and tool calls, and choose which business events to record. +title: Events +description: Watch a run, change or stop a model or tool call, and record the events that you need. sidebar: order: 4 --- -Use agent events to observe a run or make a decision before a model or tool call. Every Agent creates an `EventBus` and subscribes a `TapeRecorder`. You can add handlers through `agent.events`. +Agent events let you watch a run. They also let you decide before a model or tool call. Every Agent creates an `EventBus` and subscribes a `TapeRecorder`. You add handlers through `agent.events`. -Three kinds of information serve different purposes: +Three kinds of information have three different jobs: -| Type | Purpose | Access | +| Type | Job | How to read it | | --- | --- | --- | -| `AgentEvent` | Internal lifecycle and call interception. | `agent.events.on(...)` | -| `StreamEvent` | Output for the caller, such as text and tool progress. | Iterate the stream returned by `agent.run()`. | -| `TapeEntry` | Saved conversation and selected runtime records. | Query the [Tape](/docs/concepts/tape/). | +| `AgentEvent` | Lifecycle of the run and interception of calls. | `agent.events.on(...)` | +| `StreamEvent` | Output for the caller, such as text and tool progress. | Iterate over the stream that `agent.run()` returns. | +| `TapeEntry` | The saved conversation and selected runtime records. | Query the [tape](/docs/concepts/tape/). | -The agent's loop controls execution and invokes event handlers inline at defined points. TapeRecorder saves selected exchanges and lifecycle records; your handlers can record other events you need to keep. +The agent loop controls execution. It calls the event handlers at fixed points, before it continues. TapeRecorder saves selected exchanges and lifecycle records. Your handlers can record the other events that you need to keep. -## Register a handler +## Handlers -With the `agent` from the [SDK guide](/docs/sdk/): +This example uses the `agent` from the [SDK guide](/docs/sdk/): ```python from bub.core.events import StepFinished, ToolCallFinished @@ -39,17 +39,17 @@ def report_step(event: StepFinished) -> None: agent.events.on(StepFinished, report_step) ``` -Remove a registration with `agent.events.off(StepFinished, report_step)`. A handler for a base class receives its subclasses; a handler for `AgentEvent` receives every delivered agent event. +To remove a handler, call `agent.events.off(StepFinished, report_step)`. A handler for a base class also receives its subclasses. A handler for `AgentEvent` receives every agent event that is delivered. -For each emission, matching handlers run in registration order. The bus waits for each asynchronous handler before moving to the next. A handler's return value is ignored. Exceptions propagate to the caller of `emit()` and can fail a run. To let work continue after an observer fails, handle its exceptions inside the observer. +For each emission, the matching handlers run in the order of registration. The bus waits for each async handler before it starts the next one. The bus ignores the return value of a handler. An exception goes to the caller of `emit()` and can fail the run. If work must continue after an observer fails, catch the exception inside that observer. -This ordering applies to one emission. Concurrent tool calls or runs can emit events at the same time, so shared handler state still needs appropriate coordination. +This order applies to one emission. Concurrent tool calls or runs can emit events at the same time. Shared handler state therefore needs its own coordination. -## Change or stop a call +## Call control -To stop a call, assign a decision to the event's `decision` field: +To stop a call, assign a decision to the `decision` field of the event: ```python from bub.core.events import ToolCallDecision, ToolCallStarted @@ -61,43 +61,43 @@ def require_refund_permission(event: ToolCallStarted) -> None: event.decision = ToolCallDecision.deny("Refunds require approval.") ``` -`deny()` skips the tool and produces a tool error. `replace(result)` skips the tool and supplies a result. Both stop delivery to later handlers for that `ToolCallStarted` event. The executor still emits `ToolCallFinished` for the outcome. A model loop receives the error as a tool result; a direct command propagates it to its caller. +`deny()` skips the tool and produces a tool error. `replace(result)` skips the tool and supplies a result. Both stop delivery to the later handlers of that `ToolCallStarted` event. The executor still emits `ToolCallFinished` for the outcome. A model loop receives the error as a tool result. A direct command gives the error to its caller. -To change arguments and continue, replace `event.call` with `dataclasses.replace(event.call, arguments=...)`. To finish before a model call, set `event.decision = LLMCallDecision.finish(text)` on `LLMCallStarted`. This stops later handlers and skips the provider call. A skipped provider call does not emit `LLMCallFinished`. +To change the arguments and continue, replace `event.call` with `dataclasses.replace(event.call, arguments=...)`. To finish before a model call, set `event.decision = LLMCallDecision.finish(text)` on `LLMCallStarted`. This stops the later handlers and skips the provider call. A skipped provider call does not emit `LLMCallFinished`. -| Event | When it occurs | Supported control | +| Event | When it occurs | What you can do | | --- | --- | --- | -| `RunStarted` | A model run begins. | Observe prompt, model, and metadata. | -| `RunFinished` | Its stream closes. | Observe status, step count, elapsed time, and error. | -| `StepStarted` | A model step begins. | Observe progress. | -| `StepFinished` | A model step ends. | Observe progress; with `status == "continue"`, set `next_prompt` to give the next step a user message. | -| `MessagesPending` | A step builds its model request, or ends without tool calls. | Append user messages to `messages`; they precede the next prompt, and messages supplied at a step's end continue the run. | +| `RunStarted` | A model run begins. | Read the prompt, the model, and the metadata. | +| `RunFinished` | The stream of the run closes. | Read the status, the step count, the elapsed time, and the error. | +| `StepStarted` | A model step begins. | Read the progress. | +| `StepFinished` | A model step ends. | Read the progress. When `status == "continue"`, set `next_prompt` to give the next step a user message. | +| `MessagesPending` | A step builds its model request, or ends without tool calls. | Append user messages to `messages`. They come before the next prompt. Messages that you add at the end of a step continue the run. | | `LLMCallStarted` | Before a provider call. | Replace `request`, or set `LLMCallDecision.finish(text)`. | -| `LLMCallFinished` | A provider call completes or fails with an exception. | Observe the accumulated result; cancellation and consumer close are not reported here. | -| `ToolCallStarted` | Before a tool executes. | Replace `call`, deny, or replace the result. | +| `LLMCallFinished` | A provider call completes, or fails with an exception. | Read the accumulated result. This event does not report a cancellation or a close by the consumer. | +| `ToolCallStarted` | Before a tool runs. | Replace `call`, deny the call, or replace the result. | | `ToolCallFinished` | A tool succeeds, fails, is denied, or is replaced. | Assign `event.result.result` to change the returned result. | -| `ChatFinished` | A step's exchange is ready to record. | Observe the exchange. | -| `CommandFinished` | A direct command finishes or fails. | Observe command, status, and output. | +| `ChatFinished` | The exchange of a step is ready to record. | Read the exchange. | +| `CommandFinished` | A direct command finishes or fails. | Read the command, the status, and the output. | -Command prompts execute tools directly, emitting tool events and `CommandFinished`. See [Commands](/docs/sdk/tools/#commands). +Command prompts run tools directly. They emit tool events and `CommandFinished`. See [Commands](/docs/sdk/tools/#commands). -All agent events carry turn `state`. Events derived from `TapeEvent` also carry the active tape. For tool events, the tool context is available as `event.call.context` when the invocation has one. +All agent events carry the turn `state`. Events that derive from `TapeEvent` also carry the active tape. For tool events, the tool context is available as `event.call.context` when the call has one. -## Understand what is recorded +## Recording -Agent registers TapeRecorder before your handlers. It records `RunStarted`, `RunFinished`, `StepStarted`, `StepFinished`, `ChatFinished`, and `CommandFinished`. The `ChatFinished` handler writes messages and tool exchanges that later model calls read as context. +The Agent registers TapeRecorder before your handlers. It records `RunStarted`, `RunFinished`, `StepStarted`, `StepFinished`, `ChatFinished`, and `CommandFinished`. The `ChatFinished` handler writes the messages and tool exchanges that later model calls read as context. -Recording is part of normal agent operation. It writes to the configured store, which is in memory unless you supply another. Recording works independently of tracing configuration. +Recording is part of normal operation. It writes to the configured store. That store is in memory unless you supply another one. Recording does not depend on the tracing configuration. -To save a custom event, add a handler that writes it to the tape. Use call interception events to change requests or results; a `ChatFinished` observer runs after the default recorder has already saved that exchange. +To save a custom event, add a handler that writes it to the tape. Use the interception events to change a request or a result. A `ChatFinished` observer runs after the default recorder has saved that exchange. -## Record a business event +## Custom events -Define a dataclass derived from `AgentEvent`, or from `TapeEvent` when handlers need a tape. This example explicitly records a business event: +Define a dataclass that derives from `AgentEvent`. Derive it from `TapeEvent` when the handlers need a tape. This example records a business event: ```python from dataclasses import dataclass @@ -122,16 +122,16 @@ async def mark_reviewed() -> None: ) ``` -`emit()` returns the event after handlers have run. A custom mutable event can carry a response back to its emitter; override its `stopped` property if delivery should end once that response is set. Tools can emit through `context.events`, using the same turn state. +`emit()` returns the event after the handlers have run. A custom mutable event can carry a response back to its emitter. Override its `stopped` property if delivery must end when that response is set. Tools can emit events through `context.events`, with the same turn state. -## Use events from an application plugin +## App plugins -`AgentPlugins` connects the `before_llm_call`, `after_llm_call`, `before_tool_call`, and `after_tool_call` hooks to the call events, the `continue_prompt` hook to `StepFinished`, and the steering inbox to `MessagesPending`. Plugins return the values required by the hook contract; the adapter updates the corresponding event. BubAgent uses this adapter, and a core Agent can [use it too](/docs/sdk/app-plugins/#agent-plugins). +`AgentPlugins` connects four hooks to the call events: `before_llm_call`, `after_llm_call`, `before_tool_call`, and `after_tool_call`. It connects the `continue_prompt` hook to `StepFinished`. It connects the steering inbox to `MessagesPending`. A plugin returns the values that the hook contract requires, and the adapter updates the event. `BubAgent` uses this adapter, and a core Agent can [use it too](/docs/sdk/app-plugins/#agent-plugins). -This adapter follows plugin hook ordering and catches exceptions from individual interception hooks, logging and skipping them. To deny a call, return the appropriate decision. Other application hooks follow their own error contracts. +This adapter follows the plugin hook order. It catches exceptions from individual interception hooks, logs them, and skips them. To deny a call, return the correct decision. The other app hooks follow their own error contracts. -Use [application hooks](/docs/app/hooks/) for plugin examples and the [hooks reference](/docs/reference/hooks/) for ordering and signatures. EventBus is included in the base SDK installation. +See [Change application behavior](/docs/app/hooks/) for plugin examples. For order and signatures, see [Application hooks](/docs/reference/hooks/). EventBus is part of the base SDK installation. -`framework.events` publishes commands, channel work, and user prompts through `CommandChanged`, `WorkChanged`, and `PromptChanged` from `bub.application`. Subscribe and unsubscribe with the same `on()` and `off()` API. +`framework.events` publishes commands, channel work, and user prompts. It uses `CommandChanged`, `WorkChanged`, and `PromptChanged` from `bub.application`. You subscribe and unsubscribe with the same `on()` and `off()` API. diff --git a/website/src/content/docs/docs/concepts/extension-points.mdx b/website/src/content/docs/docs/concepts/extension-points.mdx index 542113891..b997ae18d 100644 --- a/website/src/content/docs/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/docs/concepts/extension-points.mdx @@ -1,33 +1,33 @@ --- -title: Choose an extension point -description: Decide whether your task needs a tool, event handler, skill, plugin, or channel. +title: Extension points +description: Decide whether your task needs a tool, an event handler, a skill, a plugin, or a channel. sidebar: order: 5 --- -Start with the behavior you want to add. A new action needs a tool; a new place to receive messages needs a channel. You can combine these capabilities without giving them the same job. +Start with the behavior that you want to add. A new action needs a tool. A new place to receive messages needs a channel. You can combine these capabilities, but do not give them the same job. | You want to | Use | Where it belongs | | --- | --- | --- | -| Let the agent query or change something | A [tool](/docs/sdk/tools/). | Shared core capability. Pass it to Agent, or register it with the app. | -| Observe or intercept a model or tool call | An [event handler](/docs/concepts/events/). | Core EventBus; app plugins can use the corresponding interception hooks. | +| Let the agent read or change something | A [tool](/docs/sdk/tools/). | A shared core capability. Pass it to Agent, or register it with the app. | +| Watch or intercept a model or tool call | An [event handler](/docs/concepts/events/). | The core EventBus. App plugins can use the matching interception hooks. | | Change which history becomes model messages | A [TapeContext](/docs/concepts/tape/#context). | Pass it to Agent, or provide it through an app hook. | -| Teach a reusable procedure | A [skill](/docs/app/skills/). | Discovered and loaded by the Bub application. | -| Change application setup or message processing | A [plugin](/docs/app/plugins/). | BubFramework and its hooks. | -| Connect a messaging service | A [channel](/docs/app/gateway/). | The application's inbound and outbound routing. | +| Teach a procedure that the agent can reuse | A [skill](/docs/app/skills/). | The Bub app discovers it and loads it. | +| Change the app setup or the message processing | A [plugin](/docs/app/plugins/). | `BubFramework` and its hooks. | +| Connect a messaging service | A [channel](/docs/app/gateway/). | The inbound and outbound routing of the app. | -## Actions and instructions +## Tools and skills -A tool executes Python and returns a result. A skill is a directory containing `SKILL.md` and optional supporting files. Its instructions can tell the agent how to use tools. You supply the Python implementations those instructions refer to separately. +A tool runs Python and returns a result. A skill is a directory with a `SKILL.md` file and, optionally, more files. Its instructions can tell the agent how to use tools. You write the Python functions that the instructions refer to, and you add them separately. -Plain Agent uses the tools and prompt sections you supply. The Bub application discovers skills and reads `AGENTS.md`; see [Skills](/docs/app/skills/) and [Application tools](/docs/app/tools/). +A plain Agent uses the tools and prompt sections that you give it. The Bub app discovers skills and reads `AGENTS.md`. See [Skills](/docs/app/skills/) and [Application tools](/docs/app/tools/). -A tool can also be called by a [text command](/docs/sdk/tools/#commands) or by generated Python through CodeMode. These invocation paths share the same tool implementation. +The agent can also reach a tool through a [text command](/docs/sdk/tools/#commands), or through Python that the model writes with CodeMode. All these paths use the same tool implementation. -## Messages and application state +## Messages and state -Channels deliver an inbound envelope and send outbound envelopes. The application uses these envelopes for content, routing, and session identity. Its hooks share a mutable `TurnState` dictionary for the current turn. +A channel delivers an inbound envelope and sends outbound envelopes. The app uses these envelopes for the content, the routing, and the session identity. Its hooks share a mutable `TurnState` dictionary for the current turn. -The [application turn pipeline](/docs/concepts/turn/) prepares that state and prompt before it calls the agent. Direct SDK callers supply their prompt and state to Agent themselves. +The [a turn](/docs/concepts/turn/) of the app prepares that state and the prompt before it calls the agent. A direct SDK caller gives the prompt and the state to Agent. -Choose dependencies for the capabilities you need, and choose the calling interface for your host. A Python service can reuse application plugins while retaining its own request handling and streams. [Reuse plugins in your agent](/docs/sdk/app-plugins/) shows direct component reuse and how framework hooks fit into an embedded host. +Choose the dependencies for the capabilities that you need. Then choose the interface for the host. A Python service can reuse app plugins and keep its own request handling and streams. [Reuse plugins in your agent](/docs/sdk/app-plugins/) shows how to use components directly and how the framework hooks fit into an embedded host. diff --git a/website/src/content/docs/docs/concepts/index.mdx b/website/src/content/docs/docs/concepts/index.mdx index 6ca894df6..aa2c9617d 100644 --- a/website/src/content/docs/docs/concepts/index.mdx +++ b/website/src/content/docs/docs/concepts/index.mdx @@ -1,24 +1,47 @@ --- -title: Shared concepts and capabilities -description: Understand the components shared by your own Agent and the Bub application. +title: Overview +description: The four concepts that both ways of using Bub share, the layer each one lives in, and where to read about it. sidebar: order: 0 --- -Tools, conversation history, and events work through the same core interfaces in both routes. Use these pages when you need to understand a component or choose how to connect it. You can start with [your own Agent](/docs/sdk/) or [the Bub application](/docs/app/) and return here as needed. +Bub has four concepts. A **turn** carries one message through the app. A **tape** stores the conversation. **Events** report what the loop does. An **extension point** is a place where new behavior goes. Your own Agent and the Bub app use all four. -| You want to understand | Read | -| --- | --- | -| How the core and application fit together | [Design choices](/docs/concepts/design/) | -| Which extension point fits a task | [Choose an extension point](/docs/concepts/extension-points/) | -| How to define an action or use generated Python | [Tools and CodeMode](/docs/sdk/tools/) | -| What gets saved and what the model reads | [Conversation history and context](/docs/concepts/tape/) | -| How to observe, change, or stop a call | [Events and call control](/docs/concepts/events/) | +Read the pages in any order. Each page starts with the short answer, then the details. -## A few terms +## Layers -An **Agent** runs the model and tool loop. A **run** handles one prompt and may contain several model steps. A **tool** is a typed action the agent can execute. +`core.Agent` takes a model, tools, prompt sections, and a tape store. You pass these components in yourself, so a small Python service needs only the core dependencies. -A **Tape** holds conversation records. Its **entries** include messages, tool calls, results, and events. An **anchor** marks a starting point for building context; a **handoff** writes a new anchor. The run's mutable **state** is a separate dictionary shared with tools and handlers. +`BubAgent` adds the app defaults: settings, skills, commands, tool selection, and steering. `BubFramework` manages plugins, message processing, and outbound routing around it. Use these parts when you want the behavior they provide. -The Bub application adds **plugins**, which provide Python hooks, **skills**, which provide reusable instructions, and **channels**, which carry messages in and out. Its [turn pipeline](/docs/concepts/turn/) connects those components around the agent. +`pip install bub` installs the kernel. `pip install "bub[app]"` installs both layers. The [installation guide](/docs/getting-started/install/) explains the dependency choices. + +## Concepts + +| Concept | What it is | Read | +| --- | --- | --- | +| Turn | One inbound message, and all the work that it causes. | [Turn](/docs/concepts/turn/) | +| Tape | The stored conversation, and the part of it that the model reads. | [Tape](/docs/concepts/tape/) | +| Events | The bus that reports each step, and lets a handler change the next one. | [Events](/docs/concepts/events/) | +| Extension points | Where new behavior goes: a tool, a skill, a plugin, or a channel. | [Extension points](/docs/concepts/extension-points/) | + +## Terms + +An **Agent** runs the model and tool loop. A **run** is one `agent.run()` call, and it can contain several **steps**. A **step** is one model call and the tools that it asks for. A **turn** is one inbound message and the run that answers it. + +A **tape** stores the conversation. Its **entries** are messages, tool calls, results, and events. An **anchor** marks the point where the context starts. A **handoff** writes a new anchor. The turn's **state** is a separate dictionary that tools and handlers share. + +The app adds three parts. **Plugins** provide Python hooks. **Skills** provide written instructions. **Channels** carry messages in and out. + +## Design notes + +The kernel does not read settings or plugin hooks. Hooks belong to the app: a plugin implements them, and `BubFramework` calls them. The core therefore works the same in both layers. + +History and context stay separate. The tape keeps every entry. The context selects the entries that the next model call reads. An anchor starts a new context and keeps the earlier entries, so you can read the full history and still send a short request. + +Each part has one job. A tool performs an action. A skill describes a procedure. A channel connects the app to a place where people work. These roles stay separate, so you know which part to change. + +## Background + +[Why we rewrote Bub](/posts/why-rewrite-bub/) and [Socialized evaluation and agent partnership](/posts/socialized-evaluation/) explain the earlier design. For the current interfaces, use the guides and the reference. diff --git a/website/src/content/docs/docs/concepts/tape.mdx b/website/src/content/docs/docs/concepts/tape.mdx index 7cefa0933..579a5ad49 100644 --- a/website/src/content/docs/docs/concepts/tape.mdx +++ b/website/src/content/docs/docs/concepts/tape.mdx @@ -1,19 +1,19 @@ --- -title: Conversation history and context -description: Keep a conversation on a tape, choose what the model reads, and decide what must survive a run. +title: Tape +description: Where Bub keeps the conversation, what the model reads on the next call, and what survives a run. sidebar: order: 3 --- -An agent needs both a record of its work and a useful set of messages for the next model call. Bub stores the record in a Tape and builds model context from selected entries. Keeping the two separate lets you retain history while changing what the model reads. +An agent needs two things. It needs a record of its work. It also needs a useful set of messages for the next model call. Bub keeps the record in a tape. It builds the model context from the entries that you select. Because these are separate, you can keep the full history and still send a short request. -This model is shared by `core.Agent` and `BubAgent`. Their storage defaults and session naming differ. +`core.Agent` and `BubAgent` share this model. Their default stores and session names are different. -## Choose where history lives +## Storage -Plain `Agent` creates an `InMemoryTapeStore` by default. That history lasts for the lifetime of the store. Supply a file store to keep it between processes: +A plain `Agent` creates an `InMemoryTapeStore` by default. That history lives as long as the store. To keep it between processes, pass a file store: ```python from pathlib import Path @@ -24,9 +24,9 @@ from bub.core.store import FileTapeStore agent = Agent(store=FileTapeStore(Path("sessions"))) ``` -Each `agent.run(..., tape="customer-42")` uses the named tape in that store. Reuse the name to continue a conversation; choose another to keep conversations separate. Agent uses the supplied name directly; the application has its own workspace/session naming scheme. +Each `agent.run(..., tape="customer-42")` uses the named tape in that store. Use the same name to continue a conversation. Use another name to keep conversations apart. `Agent` uses the name as given. The app has its own workspace and session naming. -`agent.tape(name)` gives you a view of that tape outside a run. Its writes go straight to the store: +`agent.tape(name)` gives you a view of a tape outside a run. Its writes go straight to the store: ```python async def inspect_history() -> None: @@ -36,34 +36,34 @@ async def inspect_history() -> None: print(len(messages), "model messages;", len(entries), "stored entries") ``` -A custom store implements `TapeStore` or `AsyncTapeStore` from `bub.core.store`. Supply it directly as `Agent(store=my_store)`. +To use your own store, implement `TapeStore` or `AsyncTapeStore` from `bub.core.store`. Pass the instance as `Agent(store=my_store)`. -| Method | Responsibility | +| Method | What it does | | --- | --- | -| `list_tapes()` | Return stored tape names. | -| `fetch_all(query)` | Return entries matching the `TapeQuery`, including its filters and ordering. | +| `list_tapes()` | Return the names of the stored tapes. | +| `fetch_all(query)` | Return the entries that match the `TapeQuery`, with its filters and order. | | `append(tape, entry)` | Append an entry to the named tape. | | `reset(tape)` | Clear the named tape. | -Use synchronous methods for `TapeStore`, asynchronous methods for `AsyncTapeStore`. The agent adapts a synchronous store for async use. Persistence and coordination across processes belong to the store and your application. +Use synchronous methods for `TapeStore` and asynchronous methods for `AsyncTapeStore`. The agent adapts a synchronous store for async use. Your store and your app handle persistence and coordination between processes. -## Choose what the model reads +## Context -Tape entries include messages, tool calls, tool results, events, and anchors. Normal recording appends entries. Use explicit operations such as `Tape.reset()` to clear history. +Tape entries are messages, tool calls, tool results, events, and anchors. Normal recording only appends entries. To clear history, call an explicit operation such as `Tape.reset()`. -The default `TapeContext` selects conversation entries after the latest anchor. It converts them to Republic messages: +The default `TapeContext` selects the conversation entries after the latest anchor. It then converts them to Republic messages: | Entry | Model context | | --- | --- | -| `anchor` | Excluded by the default query. If a custom query includes anchors, the default selector converts them to assistant messages. | +| `anchor` | The default query leaves it out. If a custom query selects anchors, the default selector converts them to assistant messages. | | `message` | The saved message. | -| `tool_call` | An assistant message carrying the calls. | -| `tool_result` | Tool messages paired with the preceding calls. | -| Other events | Omitted by the default selector. | +| `tool_call` | An assistant message that carries the calls. | +| `tool_result` | Tool messages, paired with the calls before them. | +| Other events | The default selector leaves them out. | -An anchor marks a new starting point for context. `handoff()` writes one without deleting earlier entries: +An anchor marks a new starting point for the context. `handoff()` writes one and keeps the earlier entries: ```python async def start_next_phase() -> None: @@ -73,44 +73,44 @@ async def start_next_phase() -> None: ) ``` -The `state` here is saved on the anchor, before the default query's starting point. To carry a summary into the next phase, include it in the next prompt or explicitly select it in your context strategy. Restoring values into the run's mutable state dictionary also requires your own loading logic. New sessions get a `session/start` anchor automatically. +This `state` is saved on the anchor, before the starting point of the default query. To carry a summary into the next phase, put it in the next prompt or select it in your context strategy. To load values back into the run's mutable state, write your own loader. A new session gets a `session/start` anchor automatically. -Pass `context=TapeContext(...)` to `Agent` to select another anchor or use your own `select(entries, context)` function. A selector returns Republic messages or an awaitable of them. `default_tape_context()` includes tool exchanges; a bare `TapeContext` without `select` uses its simpler message-only conversion. Entries marked `context=False` are removed before either a default or custom selector runs. +To choose another anchor, pass `context=TapeContext(...)` to `Agent`. You can also use your own `select(entries, context)` function. A selector returns Republic messages, or an awaitable of them. `default_tape_context()` includes tool exchanges. A bare `TapeContext` without `select` uses the simpler message-only conversion. Entries marked `context=False` are removed before either selector runs. -On a recognized context-length error, the agent makes one automatic handoff and retries the prompt with the shorter context. If you need a summary of the earlier history, create it before handing off. The retry can still fail if the remaining request is too large. +When the agent recognizes a context-length error, it makes one automatic handoff. It then retries the prompt with the shorter context. If you need a summary of the earlier history, create it before the handoff. The retry can still fail if the request is too large. -## Decide what survives a run +## State -The `state` passed to `agent.run()` is a mutable dictionary shared by tools, prompt sections, and event handlers during that run. Your application owns its persistence: keep durable data in your own store, or write selected facts to the tape and explicitly read them back when needed. +The `state` that you pass to `agent.run()` is a mutable dictionary. Tools, prompt sections, and event handlers share it during the run. Your app owns its persistence. Keep durable data in your own store, or write selected facts to the tape and read them back when you need them. -The default [TapeRecorder](/docs/concepts/events/#recording) saves conversation exchanges and selected lifecycle records. Record any additional variables, custom events, or decisions explicitly. +The default [TapeRecorder](/docs/concepts/events/#recording) saves conversation exchanges and selected lifecycle records. To keep other variables, custom events, or decisions, record them yourself. -## Forks and partial results +## Forks -Each `agent.run()` opens a fork of its tape. When the stream closes, the fork appends its new entries to the parent. This also happens after an exception or early cancellation. A failed run can therefore leave useful partial history. +Each `agent.run()` opens a fork of its tape. When the stream closes, the fork appends its new entries to the parent tape. This also happens after an exception or an early cancellation. A failed run can therefore leave useful partial history. -Pass `merge_back=False` to discard the fork's records on exit. This affects tape writes only. Tool side effects, such as sent requests or modified files, remain. +Pass `merge_back=False` to discard the records of the fork on exit. This affects tape writes only. Tool side effects, such as a sent request or a changed file, stay. -For a block of work you control directly, use `agent.session(name, merge_back=...)` or `tape.fork_tape(merge_back=...)`. Both merge on exit by default, including exceptional exits. +For a block of work that you control directly, use `agent.session(name, merge_back=...)` or `tape.fork_tape(merge_back=...)`. Both merge on exit by default, including exits with an exception. -Serialize runs that share a tape when they must see each other's completed work. Your application provides the session lock. The [FastAPI example](/docs/sdk/web-service/) shows locking within one process; multiple workers need coordination across processes. +If two runs share a tape and must see each other's completed work, serialize them. Your app provides the session lock. The [FastAPI example](/docs/sdk/web-service/) shows locking in one process. Multiple workers need coordination between processes. -## Keep large results alongside the conversation +## Sidecars -A `TapeSidecar` stores related data in a separate tape. Mounted sidecars follow the main tape through forks, merges, and resets, while their entries stay out of the main context. Pass sidecars explicitly to `Agent(sidecars=...)` when your application needs them. +A `TapeSidecar` stores related data in a separate tape. A mounted sidecar follows the main tape through forks, merges, and resets. Its entries stay out of the main context. Pass sidecars to `Agent(sidecars=...)` when your app needs them. -The Bub application mounts a spill sidecar for large tool results. It keeps a bounded preview and a handle in the conversation, then lets the agent read portions with `spill.read`. During a fork merge or tape reset, sidecar failures are recorded separately while the main tape operation can still complete. The app's spill limits belong to its [configuration](/docs/app/configure/). +The Bub app mounts a spill sidecar for large tool results. It keeps a short preview and a handle in the conversation. The agent can then read parts of the result with `spill.read`. If a sidecar fails during a fork merge or a tape reset, the app records the failure separately. The main tape operation can still complete. The spill limits are in the [app configuration](/docs/app/configure/). -## Storage in the Bub application +## App storage -BubAgent uses the store provided by BubFramework. The built-in application uses `FuzzyFileTapeStore` under Bub's home directory, normally `~/.bub/tapes/`. Its tape name combines hashes of the workspace path and session ID so the same session ID in different workspaces has separate history. +`BubAgent` uses the store that `BubFramework` provides. The built-in app uses `FuzzyFileTapeStore` under the Bub home directory, normally `~/.bub/tapes/`. The tape name combines hashes of the workspace path and the session ID. The same session ID in two workspaces therefore has two separate histories. -Applications replace that store through `provide_tape_store` and select context through `build_tape_context`. The [SQLAlchemy guide](/docs/integrations/storage/) demonstrates this plugin route. For a direct SDK service, pass your implementation to `Agent(store=...)` as described above. +To replace the store, an app uses `provide_tape_store`. To select the context, it uses `build_tape_context`. The [SQLAlchemy guide](/docs/integrations/storage/) shows this plugin route. For a direct SDK service, pass your implementation to `Agent(store=...)` as shown above. -The built-in app restores selected session settings from tape records. Other turn-state values need their own persistence logic. +The built-in app restores selected session settings from tape records. Other turn-state values need their own persistence. diff --git a/website/src/content/docs/docs/concepts/turn.mdx b/website/src/content/docs/docs/concepts/turn.mdx index 8c9363a50..f25d55c0e 100644 --- a/website/src/content/docs/docs/concepts/turn.mdx +++ b/website/src/content/docs/docs/concepts/turn.mdx @@ -1,48 +1,48 @@ --- -title: How the app handles a message -description: Follow a message through BubFramework, from session selection to outbound delivery. +title: Turn +description: One inbound message, from the session lookup to the reply that leaves the app. sidebar: order: 2 --- -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. +A turn is one inbound message, and all the work that it causes. `BubFramework` handles a turn: it finds the session, loads the state, builds the prompt, runs the model, and delivers the reply. Each stage has a hook, so a plugin can change that stage. -This pipeline belongs to `bub[app]`. A direct `core.Agent.run()` starts the agent loop with the prompt, state, and tape you provide. +A turn belongs to `bub[app]`. A direct `core.Agent.run()` call has no turn. The loop starts with the prompt, state, and tape that you give it. -## Follow one message +## Stages -`framework.process_inbound(message)` runs these stages: +`framework.process_inbound(message)` runs these stages in order: | 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. | +| `load_state` | Collect the turn state from the hook implementations. | +| `build_prompt` | Turn the inbound content into a prompt. | +| `run_model` / `run_model_stream` | Produce the model output, as a final result or as a stream. | +| `save_state` | Finish the work of the model stage. This 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](/docs/reference/hooks/) defines first-result hooks, combined results, and priority rules. +The built-in plugin supplies the app defaults. Other plugins can add behavior or replace it, as each hook's contract allows. The [hooks reference](/docs/reference/hooks/) defines the first-result hooks, the combined results, and the priority rules. -## Where the agent runs +## Agent call -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 built-in `run_model_stream` hook calls `BubAgent.run_stream()`. `BubAgent` uses the core model and tool loop. It adds settings, skills, commands, steering, and the app 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. +The framework converts between streaming and non-streaming hook results. `process_inbound(..., stream_output=True)` lets the channel router read the output as it arrives. It returns a complete `TurnResult` when the stream ends. -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. +A plugin can replace `run_model` and change this behavior. For example, an echo plugin can return text without an agent run. Core events and tape records occur only when the chosen implementation uses the core agent. -## Resource ownership and failures +## Resources 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`. +Use `async with framework.running():` when your hooks provide stores or other app resources. This opens the 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. +`save_state` runs when execution reaches the model stage. It also runs when that stage raises an exception. A failure in session resolution, state loading, or prompt construction occurs before the `finally` block, so it does not run `save_state`. If your plugin gets resources in those earlier stages, release them yourself. -The built-in `save_state` closes the per-message lifespan. The application records selected settings separately; saving other state requires an explicit persistence policy. +The built-in `save_state` closes the per-message lifespan. The app saves selected settings in a different place. To save other state, add your own 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. +If the model stage raises an unhandled exception, the framework runs `save_state`, then skips rendering and dispatch. The outer error path calls `on_error(stage="turn", ...)` and raises the exception again. The built-in error observer can send an error reply through outbound dispatch. A failure in one `on_error` observer does not stop the other observers. -## Extend the app +## Next steps -Use [plugin setup](/docs/app/plugins/) to register your implementation, [hook recipes](/docs/app/hooks/) for specific changes, and the [hooks reference](/docs/reference/hooks/) for exact signatures and ordering. +Use [plugin setup](/docs/app/plugins/) to register your implementation. For common changes, see [Change application behavior](/docs/app/hooks/). For exact signatures and ordering, see [Application hooks](/docs/reference/hooks/). diff --git a/website/src/content/docs/docs/integrations/acp.mdx b/website/src/content/docs/docs/integrations/acp.mdx index 223fc1b80..4cbf27d20 100644 --- a/website/src/content/docs/docs/integrations/acp.mdx +++ b/website/src/content/docs/docs/integrations/acp.mdx @@ -130,5 +130,5 @@ When the editor reopens an existing thread, `bub-acp-server` loads the matching ## Next steps - [Configure](/docs/app/configure/): Bub's model and runtime settings. -- [Turn pipeline](/docs/concepts/turn/): how Bub turns inbound messages into model calls and outbound messages. +- [Turn](/docs/concepts/turn/): how Bub turns inbound messages into model calls and outbound messages. - [Build plugins](/docs/app/plugins/): extend Bub with hooks, tools, and channels. diff --git a/website/src/content/docs/docs/reference/hooks.mdx b/website/src/content/docs/docs/reference/hooks.mdx index 93bfc5b85..c73335971 100644 --- a/website/src/content/docs/docs/reference/hooks.mdx +++ b/website/src/content/docs/docs/reference/hooks.mdx @@ -3,7 +3,7 @@ title: Application hooks description: Hook signatures, dispatch rules, and resource lifecycles for Bub plugins. --- -Hooks connect plugins to the Bub application. This reference lists the 26 hooks declared by `BubHookSpecs`. For examples, see [Change application behavior](/docs/app/hooks/); for the order of application stages, see [The application turn](/docs/concepts/turn/). +Hooks connect plugins to the Bub application. This reference lists the 26 hooks declared by `BubHookSpecs`. For examples, see [Change application behavior](/docs/app/hooks/); for the order of application stages, see [Turn](/docs/concepts/turn/). `first` below means the first non-`None` result in hook priority order. `all` means every implementation is called. An ordinary hook exception stops that dispatch and propagates; the [agent interception hooks](#agent-loop-hooks-pluginagenthooks) and `on_error` have separate exception handling. diff --git a/website/src/content/docs/zh-cn/docs/app/index.mdx b/website/src/content/docs/zh-cn/docs/app/index.mdx index 1a0f2c044..b51bbaa35 100644 --- a/website/src/content/docs/zh-cn/docs/app/index.mdx +++ b/website/src/content/docs/zh-cn/docs/app/index.mdx @@ -31,7 +31,7 @@ sidebar: | 打包自己的发行版 | [打包 Bub 应用](/zh-cn/docs/app/package/) | | 持续运行 Bub 服务 | [部署](/zh-cn/docs/app/deploy/) | -工具、会话历史和 Agent 事件遵循[共同契约](/zh-cn/docs/concepts/)。应用还提供工具加载、代码模式选择等默认策略。[Bub 应用中的工具](/zh-cn/docs/app/tools/)说明这些策略;[一轮对话](/zh-cn/docs/concepts/turn/)说明 Framework 如何连接消息接收与响应发送。 +工具、会话历史和 Agent 事件遵循[共同契约](/zh-cn/docs/concepts/)。应用还提供工具加载、代码模式选择等默认策略。[Bub 应用中的工具](/zh-cn/docs/app/tools/)说明这些策略;[轮次](/zh-cn/docs/concepts/turn/)说明 Framework 如何连接消息接收与响应发送。 ## 复用已有集成 diff --git a/website/src/content/docs/zh-cn/docs/concepts/design.mdx b/website/src/content/docs/zh-cn/docs/concepts/design.mdx deleted file mode 100644 index 724557926..000000000 --- a/website/src/content/docs/zh-cn/docs/concepts/design.mdx +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: 设计选择 -description: Bub 为什么区分 Agent 循环、应用默认行为和会话上下文。 -sidebar: - order: 1 ---- - -你可以把 Agent 内核接入已有程序,也可以定制已经能运行的 Bub 应用。两条路线使用相同的模型与工具循环,由你选择复用多少应用能力。 - -## 组合程序需要的组件 - -`core.Agent` 接收你直接提供的模型、工具、提示词片段和 Tape 存储。一个小型 Python 服务使用内核依赖即可运行。 - -`BubAgent` 增加应用的默认能力:设置、skills、命令、工具选择和运行中的消息引导。`BubFramework` 在外围管理插件、消息处理和出站路由。需要复用这些行为时,可以直接使用。 - -[安装指南](/zh-cn/docs/getting-started/install/)说明依赖选择,[SDK 指南](/zh-cn/docs/sdk/)和[应用指南](/zh-cn/docs/app/)分别展示如何使用。 - -## 分开保存历史与构建上下文 - -完整会话可能比下一步需要的上下文更长。Tape 保留已经记录的历史,上下文选择器决定模型读取什么。锚点让新的上下文从指定位置开始,同时保留此前的工作。 - -这样既能查看保存的消息和工具结果,也能让下一次请求聚焦当前任务。历史是否持久保留仍取决于选择的存储,自定义应用状态也需要明确的保存方式。详见[会话历史与上下文](/zh-cn/docs/concepts/tape/)。 - -## 明确扩展改变什么 - -工具描述一个动作,skill 描述一种工作方法,渠道把应用接到人们工作的地方。区分这些职责,才能找到适合当前任务的修改位置。 - -运行时,事件提供模型与工具调用的接入点;应用 hooks 还负责配置、资源准备和消息路由。它们各自有执行顺序和错误规则,[扩展方式指南](/zh-cn/docs/concepts/extension-points/)会引导你找到对应接口。 - -## 背景阅读 - -[为什么重写 Bub](/zh-cn/posts/why-rewrite-bub/)和[社会化评估与智能体伙伴关系](/zh-cn/posts/socialized-evaluation/)介绍项目早期的设计思路。当前接口与行为以现行指南和参考为准。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/events.mdx b/website/src/content/docs/zh-cn/docs/concepts/events.mdx index 3d0cc26c1..2d887616f 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/events.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/events.mdx @@ -1,27 +1,27 @@ --- -title: 事件与调用控制 -description: 观察 Agent 运行、拦截模型与工具调用,并选择需要记录的业务事件。 +title: 事件 +description: 看一次运行,改掉或叫停模型与工具调用,并记录你需要的事件。 sidebar: order: 4 --- -通过 Agent 事件,可以观察运行过程,也可以在模型或工具调用前作出决定。每个 Agent 都会创建 `EventBus` 并订阅 `TapeRecorder`,你可以通过 `agent.events` 添加处理器。 +Agent 事件能让你看到一次运行,也能让你在模型或工具调用之前做决定。每个 Agent 都会建一个 `EventBus`,并挂上一个 `TapeRecorder`。你通过 `agent.events` 添加处理器。 -三类信息各有用途: +三类信息,三种用途: -| 类型 | 用途 | 访问方式 | +| 类型 | 用途 | 怎么读 | | --- | --- | --- | -| `AgentEvent` | 内部生命周期通知和调用拦截。 | `agent.events.on(...)` | -| `StreamEvent` | 交给调用方的输出,例如文本和工具进度。 | 迭代 `agent.run()` 返回的流。 | -| `TapeEntry` | 保存的会话及部分运行记录。 | 查询 [Tape](/zh-cn/docs/concepts/tape/)。 | +| `AgentEvent` | 运行的生命周期,以及调用拦截。 | `agent.events.on(...)` | +| `StreamEvent` | 给调用方的输出,比如文本和工具进度。 | 迭代 `agent.run()` 返回的流。 | +| `TapeEntry` | 存下来的对话和部分运行记录。 | 查询 [tape](/zh-cn/docs/concepts/tape/)。 | -Agent 循环控制执行过程,并在指定位置直接调用事件处理器。TapeRecorder 保存选定的交互和生命周期记录;其他需要保留的事件,可以由你的处理器写入 Tape。 +循环控制执行,并在固定位置调用事件处理器,然后才往下走。TapeRecorder 保存选定的往返和生命周期记录。其他你要留的事件,由自己的处理器记录。 -## 注册处理器 +## 处理器 -下面使用 [SDK 指南](/zh-cn/docs/sdk/)中的 `agent`: +下面用的是 [SDK 指南](/zh-cn/docs/sdk/)里的 `agent`: ```python from bub.core.events import StepFinished, ToolCallFinished @@ -39,17 +39,17 @@ def report_step(event: StepFinished) -> None: agent.events.on(StepFinished, report_step) ``` -使用 `agent.events.off(StepFinished, report_step)` 移除注册。注册到基类的处理器也会接收子类事件;注册到 `AgentEvent` 的处理器可以接收所有被派发到它的 Agent 事件。 +要移除,调 `agent.events.off(StepFinished, report_step)`。注册到基类上的处理器,也会收到子类事件。注册到 `AgentEvent` 上的处理器,会收到派发给它的所有 agent 事件。 -每次派发时,匹配的处理器按注册顺序执行。总线会等待异步处理器完成,再继续下一个。处理器的返回值会被忽略,异常则传播给 `emit()` 的调用方,并可能导致运行失败。如果希望观察器失败后工作仍能继续,应在观察器内部处理异常。 +每次派发,匹配的处理器按注册顺序执行。总线会等一个异步处理器做完,再调下一个。处理器返回什么,总线都不看。异常会抛给 `emit()` 的调用方,可能让整次运行失败。如果希望某个观察器出错后工作照旧往下走,就在观察器内部把异常接住。 -这个顺序针对单次派发。并发工具调用或不同运行可能同时发出事件,因此处理器共享的状态仍需要适当协调。 +这个顺序只管一次派发。并发的工具调用或两个运行,可能同时发事件。处理器里共享的状态,仍要自己协调。 -## 修改或停止调用 +## 调用控制 -要停止调用,给事件的 `decision` 字段赋值: +要叫停一次调用,给事件的 `decision` 字段赋值: ```python from bub.core.events import ToolCallDecision, ToolCallStarted @@ -61,43 +61,43 @@ def require_refund_permission(event: ToolCallStarted) -> None: event.decision = ToolCallDecision.deny("Refunds require approval.") ``` -`deny()` 跳过工具并产生工具错误;`replace(result)` 跳过工具并提供替代结果。两者都会停止把当前 `ToolCallStarted` 交给后续处理器,执行器仍会为结果发出 `ToolCallFinished`。模型循环会把错误作为工具结果交给模型;直接命令调用则会把错误传播给调用方。 +`deny()` 跳过工具,给出一个工具错误。`replace(result)` 跳过工具,直接给出结果。两者都会让这个 `ToolCallStarted` 停止往后传。执行器仍会为结果发出 `ToolCallFinished`。模型循环把这个错误当成工具结果交给模型。直接命令调用则把错误抛给调用方。 -需要修改参数并继续执行时,用 `dataclasses.replace(event.call, arguments=...)` 替换 `event.call`。需要在模型调用前结束时,在 `LLMCallStarted` 上设置 `event.decision = LLMCallDecision.finish(text)`。这会跳过供应商调用和后续处理器;被跳过的供应商调用不会产生 `LLMCallFinished`。 +要改参数再接着跑,用 `dataclasses.replace(event.call, arguments=...)` 换掉 `event.call`。要在模型调用之前就结束,在 `LLMCallStarted` 上设 `event.decision = LLMCallDecision.finish(text)`。这会叫停后续处理器,也不去调供应商。被跳过的供应商调用不会发 `LLMCallFinished`。 -| 事件 | 发生时机 | 支持的操作 | +| 事件 | 什么时候发 | 你能做什么 | | --- | --- | --- | -| `RunStarted` | 模型运行开始。 | 观察 prompt、模型和 metadata。 | -| `RunFinished` | 对应的流关闭。 | 观察状态、步骤数、耗时和错误。 | -| `StepStarted` | 模型步骤开始。 | 观察进度。 | -| `StepFinished` | 模型步骤结束。 | 观察进度;`status == "continue"` 时,可设置 `next_prompt` 为下一步添加用户消息。 | -| `MessagesPending` | 步骤构建模型请求时,或步骤结束且没有调用工具时。 | 向 `messages` 追加用户消息,排在下一个 prompt 之前;步骤结束时提供的消息会让运行继续。 | -| `LLMCallStarted` | 调用供应商之前。 | 替换 `request`,或设置 `LLMCallDecision.finish(text)`。 | -| `LLMCallFinished` | 供应商调用完成或抛出异常。 | 观察累积结果;这里不报告取消或消费方关闭流。 | -| `ToolCallStarted` | 执行工具之前。 | 替换 `call`、拒绝调用或提供替代结果。 | -| `ToolCallFinished` | 工具成功、失败、被拒绝或被替代。 | 赋值 `event.result.result`,改变返回结果。 | -| `ChatFinished` | 本步骤的交互已准备好记录。 | 观察交互。 | -| `CommandFinished` | 直接命令完成或失败。 | 观察命令、状态和输出。 | - -命令 prompt 直接执行工具,产生工具事件和 `CommandFinished`。详见[命令](/zh-cn/docs/sdk/tools/#commands)。 - -所有 agent 事件都携带本轮 `state`。继承 `TapeEvent` 的事件还携带当前 Tape。工具调用具有上下文时,可以通过 `event.call.context` 访问。 +| `RunStarted` | 一次模型运行开始。 | 读 prompt、模型和 metadata。 | +| `RunFinished` | 这次运行的流关闭。 | 读状态、步骤数、耗时和错误。 | +| `StepStarted` | 一个模型步骤开始。 | 读进度。 | +| `StepFinished` | 一个模型步骤结束。 | 读进度。`status == "continue"` 时,设 `next_prompt`,给下一步加一条用户消息。 | +| `MessagesPending` | 步骤构建模型请求时,或步骤结束时没有调工具。 | 往 `messages` 追加用户消息,排在下一个 prompt 之前。在步骤结束时加的消息,会让运行继续。 | +| `LLMCallStarted` | 调供应商之前。 | 换掉 `request`,或设 `LLMCallDecision.finish(text)`。 | +| `LLMCallFinished` | 供应商调用完成,或抛异常。 | 读累积的结果。取消和消费方关流,这里不报。 | +| `ToolCallStarted` | 工具执行之前。 | 换掉 `call`,拒绝调用,或换掉结果。 | +| `ToolCallFinished` | 工具成功、失败、被拒绝或被替换。 | 给 `event.result.result` 赋值,改掉返回的结果。 | +| `ChatFinished` | 这一步的往返可以记录了。 | 读这一次往返。 | +| `CommandFinished` | 直接命令完成或失败。 | 读命令、状态和输出。 | + +命令 prompt 直接跑工具,会发出工具事件和 `CommandFinished`。见[命令](/zh-cn/docs/sdk/tools/#commands)。 + +所有 agent 事件都带本轮的 `state`。从 `TapeEvent` 派生的事件还带当前 tape。工具事件在调用有上下文时,可以从 `event.call.context` 取到。 -## 了解默认记录范围 +## 记录 -Agent 在你的处理器之前注册 TapeRecorder。它记录 `RunStarted`、`RunFinished`、`StepStarted`、`StepFinished`、`ChatFinished` 和 `CommandFinished`。其中 `ChatFinished` 处理器保存消息与工具交互,供后续模型调用构建上下文。 +Agent 在你的处理器之前注册 TapeRecorder。它记录 `RunStarted`、`RunFinished`、`StepStarted`、`StepFinished`、`ChatFinished` 和 `CommandFinished`。`ChatFinished` 这个处理器写入消息和工具往返,后面的模型调用就靠它拿上下文。 -记录属于 Agent 的正常运行过程,写入你配置的存储;未指定存储时写入内存。记录功能独立于 tracing 配置。 +记录是正常流程的一部分。它写进你配置的存储,没另外指定就写在内存里。记录和 tracing 的配置无关。 -要保存自定义事件,可以添加处理器,将它写入 Tape。修改请求或结果应使用调用拦截事件;默认 Recorder 已经保存交互后,才会执行你的 `ChatFinished` 观察器。 +要存自定义事件,加一个处理器把它写进 tape。要改请求或结果,用调用拦截事件。`ChatFinished` 的观察器运行时,默认记录器已经把这次往返存好了。 -## 记录业务事件 +## 自定义事件 -定义继承 `AgentEvent` 的 dataclass;如果处理器需要 Tape,就继承 `TapeEvent`。下面显式记录一项业务事件: +定义一个继承 `AgentEvent` 的 dataclass。处理器需要 tape 时,就继承 `TapeEvent`。下面这个例子记一条业务事件: ```python from dataclasses import dataclass @@ -122,16 +122,16 @@ async def mark_reviewed() -> None: ) ``` -`emit()` 返回处理器执行后的事件。自定义可变事件可以把结果带回发出方;如果结果确定后就应结束派发,可以覆写 `stopped` 属性。工具可以通过 `context.events` 发出事件,并传入相同的本轮状态。 +`emit()` 在处理器跑完后返回事件。自定义的可变事件可以把结果带回发出方。结果一确定就该停止派发时,覆写它的 `stopped` 属性。工具可以通过 `context.events` 发事件,用的是同一份本轮状态。 -## 从应用插件接入事件 +## 应用插件 -`AgentPlugins` 将 `before_llm_call`、`after_llm_call`、`before_tool_call`、`after_tool_call` hooks 接到调用事件上,将 `continue_prompt` hook 接到 `StepFinished` 上,将 steering inbox 接到 `MessagesPending` 上。插件按 hook 要求返回值,由适配器更新对应事件。BubAgent 使用这个适配器,核心 Agent [同样可以使用](/zh-cn/docs/sdk/app-plugins/#agent-plugins)。 +`AgentPlugins` 把四个 hook 接到调用事件上:`before_llm_call`、`after_llm_call`、`before_tool_call`、`after_tool_call`。它把 `continue_prompt` 接到 `StepFinished`,把 steering inbox 接到 `MessagesPending`。插件按 hook 的要求返回值,适配器去改对应的事件。`BubAgent` 用的就是这个适配器,核心 Agent [也可以这么用](/zh-cn/docs/sdk/app-plugins/#agent-plugins)。 -该适配器遵循插件 hook 顺序,捕获单个拦截 hook 的异常,记录后跳过。拒绝调用应返回对应 decision。其他应用 hooks 遵循各自的错误处理规则。 +这个适配器遵循插件 hook 的顺序。单个拦截 hook 抛异常时,它接住、记日志、跳过。要拒绝一次调用,就返回对应的 decision。其他应用 hooks 按各自的出错规则处理。 -[应用 Hooks 指南](/zh-cn/docs/app/hooks/)提供插件示例,[Hooks 参考](/zh-cn/docs/reference/hooks/)列出顺序与签名。EventBus 包含在基础 SDK 安装中。 +插件示例见[改变应用行为](/zh-cn/docs/app/hooks/)。顺序和签名见[应用 Hooks](/zh-cn/docs/reference/hooks/)。EventBus 在基础 SDK 安装里就有。 -`framework.events` 通过 `bub.application` 中的 `CommandChanged`、`WorkChanged` 和 `PromptChanged` 发布命令、通道工作和用户提示,沿用相同的 `on()` 与 `off()` 接口订阅和取消订阅。 +`framework.events` 发布命令、渠道工作和用户输入,用的是 `bub.application` 里的 `CommandChanged`、`WorkChanged` 和 `PromptChanged`。订阅和取消订阅,还是 `on()` 和 `off()`。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx index 3ad6b278f..9123cca2d 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx @@ -1,33 +1,33 @@ --- -title: 选择扩展方式 -description: 根据任务选择工具、事件处理器、skill、插件或渠道。 +title: 扩展点 +description: 判断你的任务需要工具、事件处理器、skill、插件,还是渠道。 sidebar: order: 5 --- -从要增加的行为出发。执行新动作需要工具,接收另一种来源的消息需要渠道。这些能力可以一起使用,各自保留清楚的职责。 +先想清楚你要加什么行为。加一个新动作,用工具。加一个新的收消息的地方,用渠道。这些能力可以叠着用,但别让它们干同一件事。 -| 想完成什么 | 使用什么 | 属于哪一层 | +| 你想 | 用 | 它在哪一层 | | --- | --- | --- | -| 让 Agent 查询或修改内容 | [工具](/zh-cn/docs/sdk/tools/)。 | 公共内核能力。直接传给 Agent,或在应用中注册。 | -| 观察或拦截模型、工具调用 | [事件处理器](/zh-cn/docs/concepts/events/)。 | Core EventBus;应用插件也可以使用对应的拦截 hooks。 | -| 改变哪些历史进入模型消息 | [TapeContext](/zh-cn/docs/concepts/tape/#context)。 | 直接传给 Agent,或通过应用 hook 提供。 | -| 教会 Agent 一种可复用的工作方法 | [skill](/zh-cn/docs/app/skills/)。 | 由 Bub 应用发现和加载。 | -| 改变应用初始化或消息处理 | [插件](/zh-cn/docs/app/plugins/)。 | BubFramework 及其 hooks。 | -| 接入消息服务 | [渠道](/zh-cn/docs/app/gateway/)。 | 应用的入站与出站路由。 | +| 让 agent 读或改某个东西 | [工具](/zh-cn/docs/sdk/tools/)。 | 内核的通用能力。传给 Agent,或者在应用里注册。 | +| 看住或拦下模型、工具调用 | [事件处理器](/zh-cn/docs/concepts/events/)。 | 内核的 EventBus。应用插件可以用对应的拦截 hooks。 | +| 改哪段历史变成模型消息 | [TapeContext](/zh-cn/docs/concepts/tape/#context)。 | 传给 Agent,或者通过应用 hook 提供。 | +| 教 agent 一套可复用的做法 | [skill](/zh-cn/docs/app/skills/)。 | Bub 应用负责发现和加载。 | +| 改应用怎么初始化,或者消息怎么处理 | [插件](/zh-cn/docs/app/plugins/)。 | `BubFramework` 和它的 hooks。 | +| 接一个消息服务 | [渠道](/zh-cn/docs/app/gateway/)。 | 应用的入站和出站路由。 | -## 动作与指令 +## 工具与 skill -工具执行 Python 并返回结果。skill 是包含 `SKILL.md` 及可选辅助文件的目录。它的指令可以说明怎样使用工具,其中提到的 Python 工具实现需要单独提供。 +工具跑 Python,返回结果。skill 是一个目录,里面有 `SKILL.md`,还可以有别的文件。它的指令可以告诉 agent 怎么用工具。指令里提到的 Python 函数,你自己写,再另外挂上去。 -普通 Agent 使用你提供的工具和提示词片段。Bub 应用负责发现 skills 和读取 `AGENTS.md`,详见 [Skills](/zh-cn/docs/app/skills/)和[应用工具](/zh-cn/docs/app/tools/)。 +普通的 Agent 只用你给的工具和提示词片段。Bub 应用会去发现 skills,也会读 `AGENTS.md`。见 [Skills](/zh-cn/docs/app/skills/)和[应用工具](/zh-cn/docs/app/tools/)。 -工具也可以通过[文本命令](/zh-cn/docs/sdk/tools/#commands)调用,或由 CodeMode 中生成的 Python 调用。这些调用路径共用同一个工具实现。 +agent 也可以用一条[文本命令](/zh-cn/docs/sdk/tools/#commands)调工具,或者用 CodeMode 让模型写 Python 来调。这几条路走的是同一个工具实现。 -## 消息与应用状态 +## 消息与状态 -渠道接收入站 envelope 并发送出站 envelope。应用从中读取内容、路由信息和会话标识。各 hooks 共用当前轮次的可变 `TurnState` 字典。 +渠道收一个入站 envelope,发出站 envelope。应用用 envelope 带内容、路由和会话标识。各个 hooks 共用当前这一轮的 `TurnState` 字典。 -[应用消息处理流程](/zh-cn/docs/concepts/turn/)先准备状态和 prompt,再调用 Agent。直接使用 SDK 时,由调用方把 prompt 和 state 传给 Agent。 +[轮次](/zh-cn/docs/concepts/turn/)先把状态和 prompt 准备好,再调 agent。直接用 SDK 的人,自己把 prompt 和 state 交给 Agent。 -按需要的能力选择依赖,按宿主的处理方式选择调用入口。Python 服务可以复用应用插件,同时保留自己的请求处理和流接口。[在 Agent 中复用插件](/zh-cn/docs/sdk/app-plugins/)会说明怎样直接使用插件组件,以及如何在宿主中接入 framework hooks。 +先按要用的能力选依赖,再按宿主选调用入口。一个 Python 服务可以复用应用插件,同时保留自己的请求处理和流。[在 Agent 里复用插件](/zh-cn/docs/sdk/app-plugins/)讲怎么直接用组件,也讲 framework hooks 在嵌入的宿主里怎么接。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/index.mdx b/website/src/content/docs/zh-cn/docs/concepts/index.mdx index 715f56ef6..5f97ab3dd 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/index.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/index.mdx @@ -1,24 +1,47 @@ --- -title: 公共概念与能力 -description: 理解自己的 Agent 与 Bub 应用共用的组件。 +title: 概览 +description: 两种用法共用的四个概念,各自在哪一层,以及去哪一页读。 sidebar: order: 0 --- -两条路线使用相同的工具、会话历史和事件接口。需要理解组件或选择接入方式时,可以查阅这些页面。也可以先从[自己的 Agent](/zh-cn/docs/sdk/) 或 [Bub 应用](/zh-cn/docs/app/)开始,遇到具体问题再回到这里。 +Bub 有四个概念。**轮次**把一条消息送过整个应用。**tape** 存对话。**事件**报告循环做了什么。**扩展点**是新行为该放的地方。你自己的 Agent 和 Bub 应用都用这四个。 -| 想了解什么 | 阅读 | -| --- | --- | -| 内核与应用如何配合 | [设计选择](/zh-cn/docs/concepts/design/) | -| 当前任务适合哪个扩展点 | [选择扩展方式](/zh-cn/docs/concepts/extension-points/) | -| 怎样定义动作或使用生成的 Python | [工具与 CodeMode](/zh-cn/docs/sdk/tools/) | -| 保存哪些信息,模型读取哪些信息 | [会话历史与上下文](/zh-cn/docs/concepts/tape/) | -| 怎样观察、修改或停止调用 | [事件与调用控制](/zh-cn/docs/concepts/events/) | +顺序随意。每一页先给结论,再展开。 -## 几个常用术语 +## 分层 -**Agent** 执行模型与工具循环。一轮**运行(run)**处理一个 prompt,可能包含多个模型步骤。**工具(tool)**是 Agent 可以执行的带类型动作。 +`core.Agent` 接收模型、工具、提示词片段和一个 tape 存储。这些都由你传进来。一个小的 Python 服务,只装内核的依赖就能跑。 -**Tape** 保存会话记录,**条目(entry)**包括消息、工具调用、结果和事件。**锚点(anchor)**标记上下文的起点,**handoff** 写入新锚点。本轮可变**状态(state)**是另一个字典,由工具和处理器共用。 +`BubAgent` 加上应用的默认行为:配置、skills、命令、工具选择和 steering。`BubFramework` 在外围管理插件、消息处理和出站路由。需要这些行为时,再引入它们。 -Bub 应用还提供用于接入 Python hooks 的**插件**、提供可复用指令的 **skills**,以及收发消息的**渠道**。[应用消息处理流程](/zh-cn/docs/concepts/turn/)将这些组件连接到 Agent 周围。 +`pip install bub` 只装内核。`pip install "bub[app]"` 两层都装。[安装指南](/zh-cn/docs/getting-started/install/)说明依赖怎么选。 + +## 概念 + +| 概念 | 是什么 | 读哪页 | +| --- | --- | --- | +| 轮次 | 一条进来的消息,以及它引起的全部工作。 | [轮次](/zh-cn/docs/concepts/turn/) | +| Tape | 存下来的对话,以及模型实际读到的那一段。 | [Tape](/zh-cn/docs/concepts/tape/) | +| 事件 | 报告每一步的总线,处理器可以借它改掉下一步。 | [事件](/zh-cn/docs/concepts/events/) | +| 扩展点 | 新行为放的地方:工具、skill、插件或渠道。 | [扩展点](/zh-cn/docs/concepts/extension-points/) | + +## 术语 + +**Agent** 跑模型与工具的循环。一次**运行**(run)就是一次 `agent.run()` 调用,里面可以有好几个**步骤**(step)。一个**步骤**是一次模型调用,以及它要求的工具。一个**轮次**(turn)是一条进来的消息,和回答它的那次运行。 + +**tape** 存对话。它的**条目**是消息、工具调用、结果和事件。**anchor** 标出上下文的起点。**handoff** 会写一个新的 anchor。本轮的 **state** 是一个独立字典,工具和处理器共用它。 + +应用另外加三样东西。**插件**提供 Python hooks。**skills** 写指令。**渠道**收发消息。 + +## 设计说明 + +内核不读配置,也不读插件的 hooks。hooks 属于应用:插件实现它们,`BubFramework` 调用它们。所以内核在两层里的行为一样。 + +历史与上下文分开。tape 留下每一条。上下文挑出下一次模型调用要读的条目。anchor 开一个新的上下文,前面的条目照旧保留。因此你可以查完整历史,同时只发一个短请求。 + +每个部分只干一件事。工具做动作。skill 写做法。渠道把应用接到人干活的地方。职责分开,你才知道该改哪一块。 + +## 背景 + +[为什么重写 Bub](/zh-cn/posts/why-rewrite-bub/)和[社会化评估与智能体伙伴关系](/zh-cn/posts/socialized-evaluation/)讲了早期设计的来由。当前的接口和行为,以现在的指南和参考为准。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/tape.mdx b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx index 709186662..bd19363a6 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/tape.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx @@ -1,19 +1,19 @@ --- -title: 会话历史与上下文 -description: 用 Tape 保存会话,选择模型读取的内容,并决定哪些信息需要跨运行保留。 +title: Tape +description: 对话存在哪里,下一次调用模型读到什么,以及一次运行结束后还能剩下什么。 sidebar: order: 3 --- -Agent 既需要工作记录,也需要适合下一次模型调用的消息。Bub 用 Tape 保存记录,再从中选择条目构建模型上下文。这样可以保留历史,同时调整模型读取的内容。 +Agent 需要两样东西。一是自己干过什么的记录,二是下一次模型调用要用的一组消息。Bub 把记录存进 tape,再用你选中的条目拼出模型上下文。两者分开,你就能留下完整历史,同时只发一个短请求。 -`core.Agent` 和 `BubAgent` 共用这套机制,但默认存储和会话命名方式不同。 +`core.Agent` 和 `BubAgent` 共用这套机制。两者的默认存储和会话名不一样。 -## 选择历史存储位置 +## 存储 -普通 `Agent` 默认创建 `InMemoryTapeStore`,历史只在该存储实例存活期间保留。传入文件存储,可以在程序重启后继续使用: +普通的 `Agent` 默认创建 `InMemoryTapeStore`。历史跟着这个存储一起活,存储没了就没了。要在进程之间保留,传一个文件存储: ```python from pathlib import Path @@ -24,9 +24,9 @@ from bub.core.store import FileTapeStore agent = Agent(store=FileTapeStore(Path("sessions"))) ``` -每次 `agent.run(..., tape="customer-42")` 都使用这个存储中的指定 Tape。复用名称可以继续会话;使用不同名称可以隔离会话。Agent 直接使用你传入的名称;应用层有自己的工作区与会话命名规则。 +每次 `agent.run(..., tape="customer-42")` 用的都是这个存储里名为 `customer-42` 的 tape。名字相同就接着上一次聊,名字不同就分开。`Agent` 原样使用你给的名字。应用有自己的工作区与会话命名规则。 -`agent.tape(name)` 可以在运行之外访问 Tape,写入会直接进入存储: +`agent.tape(name)` 让你在一次运行之外看这个 tape。它的写入直接进存储: ```python async def inspect_history() -> None: @@ -36,34 +36,34 @@ async def inspect_history() -> None: print(len(messages), "model messages;", len(entries), "stored entries") ``` -自定义存储需要实现 `bub.core.store` 中的 `TapeStore` 或 `AsyncTapeStore`。直接传给 `Agent(store=my_store)` 即可。 +要自己写存储,实现 `bub.core.store` 里的 `TapeStore` 或 `AsyncTapeStore`。把实例传给 `Agent(store=my_store)` 就行。 -| 方法 | 职责 | +| 方法 | 做什么 | | --- | --- | -| `list_tapes()` | 返回已保存的 Tape 名称。 | -| `fetch_all(query)` | 按 `TapeQuery` 的过滤与顺序规则返回条目。 | -| `append(tape, entry)` | 向指定 Tape 追加条目。 | -| `reset(tape)` | 清空指定 Tape。 | +| `list_tapes()` | 返回已保存的 tape 名称。 | +| `fetch_all(query)` | 按 `TapeQuery` 的过滤和排序返回条目。 | +| `append(tape, entry)` | 往指定的 tape 追加一条。 | +| `reset(tape)` | 清空指定的 tape。 | -`TapeStore` 使用同步方法,`AsyncTapeStore` 使用异步方法。Agent 会把同步存储适配为异步调用。持久化和跨进程协调由存储与应用负责。 +`TapeStore` 用同步方法,`AsyncTapeStore` 用异步方法。Agent 会把同步存储适配成异步调用。持久化和跨进程协调,由你的存储和应用负责。 -## 选择模型读取的内容 +## 上下文 -Tape 条目包括消息、工具调用、工具结果、事件和锚点。正常记录会追加条目;需要清空历史时,可以显式调用 `Tape.reset()` 等操作。 +tape 的条目有消息、工具调用、工具结果、事件和 anchor。正常记录只追加。要清空历史,得显式调用 `Tape.reset()` 这类操作。 -默认 `TapeContext` 选择最近锚点之后的会话条目,并转换成 Republic 消息: +默认的 `TapeContext` 选出最近一个 anchor 之后的会话条目,再转成 Republic 消息: | 条目 | 模型上下文 | | --- | --- | -| `anchor` | 默认查询不包含锚点。自定义查询包含锚点时,默认选择器将其转换为 assistant 消息。 | -| `message` | 保存的消息。 | -| `tool_call` | 包含工具调用的 assistant 消息。 | -| `tool_result` | 与前面的调用配对的工具消息。 | -| 其他事件 | 默认选择器忽略。 | +| `anchor` | 默认查询不含它。如果自定义查询选了 anchor,默认选择器会把它转成 assistant 消息。 | +| `message` | 保存的那条消息。 | +| `tool_call` | 一条 assistant 消息,带上这些调用。 | +| `tool_result` | 工具消息,与前面的调用配对。 | +| 其他事件 | 默认选择器跳过。 | -锚点标记上下文的新起点。`handoff()` 写入锚点,但保留此前的历史: +anchor 标出上下文的新起点。`handoff()` 写一个 anchor,前面的条目照旧保留: ```python async def start_next_phase() -> None: @@ -73,44 +73,44 @@ async def start_next_phase() -> None: ) ``` -这里的 `state` 保存在锚点上,位于默认查询的起点之前。要在下一阶段继续使用摘要,需要将它放入下一次 prompt,或在自己的上下文策略中显式选择。将其中的值恢复到本轮可变状态字典,也需要自己编写加载逻辑。新会话会自动获得一个 `session/start` 锚点。 +这里的 `state` 存在 anchor 上,位置在默认查询的起点之前。要把摘要带进下一阶段,就把它放进下一次 prompt,或者在你的上下文策略里显式选它。要把值装回本轮的可变状态,得自己写加载逻辑。新会话会自动得到一个 `session/start` anchor。 -向 `Agent` 传入 `context=TapeContext(...)`,可以选择其他锚点,或提供自己的 `select(entries, context)` 函数。选择器返回 Republic 消息,也支持异步结果。`default_tape_context()` 包含工具交互;未指定 `select` 的普通 `TapeContext` 使用更简单的消息转换。标记为 `context=False` 的条目会在默认或自定义选择器运行前排除。 +想换一个 anchor,就给 `Agent` 传 `context=TapeContext(...)`。也可以用自己的 `select(entries, context)` 函数。选择器返回 Republic 消息,或者返回它们的 awaitable。`default_tape_context()` 含工具往返;不带 `select` 的 `TapeContext` 用更简单的消息转换。带 `context=False` 的条目,在默认或自定义选择器运行之前就被剔除。 -遇到可识别的上下文长度错误时,Agent 会自动 handoff 一次,再用缩短后的上下文重试。需要保留前面历史的摘要时,应在 handoff 前生成。剩余请求仍然太大时,重试也可能失败。 +Agent 认出上下文长度错误时,会自动 handoff 一次,然后用更短的上下文重试。要保留前面历史的摘要,就得在 handoff 之前生成。如果剩下的请求还是太大,重试仍会失败。 -## 决定哪些信息需要保留 +## 状态 -传给 `agent.run()` 的 `state` 是可变字典,由本轮的工具、提示词片段和事件处理器共用。持久化由你的应用负责:可以把业务数据放入自己的存储,也可以把选定事实写到 Tape,在需要时显式读回。 +传给 `agent.run()` 的 `state` 是一个可变字典。本轮的工具、提示词片段和事件处理器共用它。持久化由你的应用负责。长期数据放进自己的存储,或者把选定的事实写进 tape,需要时再读回来。 -默认 [TapeRecorder](/zh-cn/docs/concepts/events/#recording)保存会话交互和部分生命周期记录。需要保留的其他变量、自定义事件或决策,由应用显式记录。 +默认的 [TapeRecorder](/zh-cn/docs/concepts/events/#recording)保存会话往返和一部分生命周期记录。其他变量、自定义事件和决策,要自己记录。 -## 分支与部分结果 +## 分支 -每次 `agent.run()` 都打开当前 Tape 的分支。关闭流时,分支会把新条目追加到父 Tape。异常退出或提前取消也会合并,因此失败的运行仍可能留下有用的部分历史。 +每次 `agent.run()` 都会给它的 tape 开一个分支。流关闭时,分支把新条目追加到父 tape。抛异常或提前取消也一样。所以一次失败的运行,也可能留下有用的部分历史。 -传入 `merge_back=False`,可以在退出时丢弃分支记录。它只影响 Tape 写入。工具已经发送的请求、修改的文件等副作用仍然保留。 +传 `merge_back=False`,退出时丢掉分支的记录。这只影响 tape 写入。工具已经产生的副作用,比如发出的请求、改过的文件,都还在。 -自己管理一段工作时,可以使用 `agent.session(name, merge_back=...)` 或 `tape.fork_tape(merge_back=...)`。两者默认都在退出时合并,包括异常退出。 +自己管一段工作时,用 `agent.session(name, merge_back=...)` 或 `tape.fork_tape(merge_back=...)`。两者默认都在退出时合并,异常退出也合并。 -如果同一 Tape 上的运行需要看到前一轮的完整结果,应让它们依次执行。会话锁由你的应用提供。[FastAPI 示例](/zh-cn/docs/sdk/web-service/)展示单进程内的锁;多个 worker 需要跨进程协调。 +两个运行共用一个 tape,又要看到对方做完的结果,就把它们串起来。会话锁由你的应用提供。[FastAPI 示例](/zh-cn/docs/sdk/web-service/)演示了单进程内的加锁。多个 worker 之间要另外协调。 -## 将大型结果放在会话旁边 +## Sidecar -`TapeSidecar` 用独立的 Tape 保存关联数据。挂载的 sidecar 随主 Tape 一起分支、合并和重置,其条目不进入主上下文。自己的应用需要这项能力时,可以通过 `Agent(sidecars=...)` 显式传入。 +`TapeSidecar` 把相关的数据存在另一个 tape 里。挂载的 sidecar 跟着主 tape 一起分支、合并和重置。它的条目不进主上下文。你的应用需要时,用 `Agent(sidecars=...)` 传进去。 -Bub 应用为大型工具结果挂载 spill sidecar。会话中保留有限预览和句柄,Agent 可以通过 `spill.read` 分段读取。合并分支或重置 Tape 时,sidecar 的失败会单独记录,主 Tape 操作仍可完成。应用的 spill 限制属于[配置](/zh-cn/docs/app/configure/)。 +Bub 应用给大型工具结果挂了一个 spill sidecar。会话里留一小段预览和一个句柄,agent 可以用 `spill.read` 分段读。合并分支或重置 tape 时,sidecar 的失败单独记录,主 tape 的操作照样完成。spill 的上限在[应用配置](/zh-cn/docs/app/configure/)里。 -## Bub 应用中的存储 +## 应用存储 -BubAgent 使用 BubFramework 提供的存储。内置应用使用 Bub home 下的 `FuzzyFileTapeStore`,通常位于 `~/.bub/tapes/`。Tape 名称由工作区路径和会话 ID 的哈希组合而成,因此不同工作区中相同的会话 ID 拥有独立历史。 +`BubAgent` 用的是 `BubFramework` 给的存储。内置应用用 Bub home 目录下的 `FuzzyFileTapeStore`,通常是 `~/.bub/tapes/`。tape 名称由工作区路径和会话 ID 的哈希拼成。所以同一个会话 ID 在两个工作区里,历史是分开的。 -应用通过 `provide_tape_store` 替换存储,通过 `build_tape_context` 选择上下文。[SQLAlchemy 指南](/zh-cn/docs/integrations/storage/)展示这条插件路线。直接使用 SDK 的服务则按本页前面的方式,把实现传给 `Agent(store=...)`。 +要换存储,应用用 `provide_tape_store`。要选上下文,用 `build_tape_context`。[SQLAlchemy 指南](/zh-cn/docs/integrations/storage/)演示了这条插件路线。直接用 SDK 的服务,按前面说的把实现传给 `Agent(store=...)`。 -内置应用会从 Tape 记录恢复选定的会话设置。其他本轮状态需要单独实现持久化。 +内置应用会从 tape 记录里恢复部分会话设置。其他本轮状态要自己持久化。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx index d471a687e..c2b326e01 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx @@ -1,48 +1,48 @@ --- -title: 应用如何处理消息 -description: 跟随一条消息经过 BubFramework,了解会话选择、Agent 调用和出站交付。 +title: 轮次 +description: 一条进来的消息,从找会话到把回复发出去。 sidebar: order: 2 --- -BubFramework 将 Agent 接到应用所需的会话标识、本轮状态、prompt 准备和消息交付上。每个阶段都提供 hooks,插件可以在对应位置改变应用行为。 +一个轮次就是一条进来的消息,以及它引起的全部工作。`BubFramework` 负责一个轮次:找会话、装载状态、构建 prompt、跑模型、发出回复。每个阶段都留了 hook,插件可以改这一段。 -这条流程属于 `bub[app]`。直接调用 `core.Agent.run()` 时,Agent 使用你提供的 prompt、state 和 Tape 开始循环。 +轮次属于 `bub[app]`。直接调 `core.Agent.run()` 没有轮次:循环就用你给的 prompt、state 和 tape 开始。 -## 一条消息经过哪些阶段 +## 阶段 -`framework.process_inbound(message)` 依次执行以下阶段: +`framework.process_inbound(message)` 按下面的顺序执行: -| 阶段 | 职责 | +| 阶段 | 做什么 | | --- | --- | -| `resolve_session` | 为入站消息选择会话 ID。 | -| `load_state` | 汇集各 hook 提供的本轮状态。 | -| `build_prompt` | 将入站内容转换为 prompt。 | -| `run_model` / `run_model_stream` | 通过完整结果或流生成模型输出。 | -| `save_state` | 完成与模型阶段关联的收尾工作,在该阶段的 `finally` 块中运行。 | -| `render_outbound` | 将结果转换为出站 envelope。 | -| `dispatch_outbound` | 交付每个出站 envelope。 | +| `resolve_session` | 为收到的消息选出会话 ID。 | +| `load_state` | 从各个 hook 收集本轮状态。 | +| `build_prompt` | 把收到的内容转成 prompt。 | +| `run_model` / `run_model_stream` | 产出模型输出:完整结果,或者一个流。 | +| `save_state` | 收尾模型阶段的工作。它在自己的 `finally` 块里运行。 | +| `render_outbound` | 把结果转成出站 envelope。 | +| `dispatch_outbound` | 发出每一个出站 envelope。 | -内置插件提供应用默认行为。其他插件按各 hook 的规则补充或替换行为;[Hooks 参考](/zh-cn/docs/reference/hooks/)说明首个有效结果、结果合并和优先级。 +内置插件提供应用的默认行为。其他插件可以在 hook 允许的范围内补充或替换行为。[Hooks 参考](/zh-cn/docs/reference/hooks/)定义了首个有效结果、结果合并和优先级。 -## Agent 在哪里运行 +## Agent 调用 -内置 `run_model_stream` hook 调用 `BubAgent.run_stream()`。BubAgent 使用内核的模型与工具循环,并增加设置、skills、命令、运行中消息引导和应用工具策略。 +内置的 `run_model_stream` hook 调用 `BubAgent.run_stream()`。`BubAgent` 用的是内核的模型与工具循环,再加上配置、skills、命令、steering 和应用的工具策略。 -框架负责适配流式和非流式 hook 结果。`process_inbound(..., stream_output=True)` 让渠道路由器消费增量输出,并在消费结束后返回已完成的 `TurnResult`。 +框架在流式与非流式 hook 结果之间做转换。`process_inbound(..., stream_output=True)` 让渠道路由器边收边处理,流结束时返回一个完整的 `TurnResult`。 -插件替换 `run_model` 后,执行行为随之改变。例如,echo 插件可以直接返回文本,不创建 Agent 运行。只有选中的实现实际使用内核 Agent,才会产生相应的核心事件与 Tape 记录。 +插件替换 `run_model` 就会改掉这一步。比如一个 echo 插件可以直接返回文本,根本不创建 agent run。只有选中的实现真的用了内核 agent,才会产生核心事件和 tape 记录。 -## 资源由谁管理,失败如何处理 +## 资源和失败 -当 hooks 提供存储或其他应用资源时,使用 `async with framework.running():` 打开它们,并在退出时释放。内置的单条消息 lifespan 在加载状态时打开,在 `save_state` 中关闭。 +当 hooks 要提供存储或其他应用资源时,用 `async with framework.running():`。它打开这些资源,退出时释放。内置的单条消息 lifespan 在装载状态时打开,在 `save_state` 里关闭。 -执行到模型阶段后,无论该阶段是否抛出异常,都会运行 `save_state`。会话解析、状态加载或 prompt 构建失败发生在这个 `finally` 块之前,因此不会触发它。插件在较早阶段获取资源时,需要考虑这个边界。 +执行进到模型阶段后,`save_state` 就会运行,模型阶段抛异常也一样。会话解析、状态装载或 `build_prompt` 的失败发生在这个 `finally` 块之前,所以不会触发它。插件如果在这几个较早的阶段拿了资源,要自己释放。 -内置 `save_state` 负责关闭单条消息 lifespan。应用会另外记录选定设置,保存其他状态需要明确的持久化方式。 +内置的 `save_state` 关闭单条消息 lifespan。应用另外保存选定的设置。要保存其他状态,得自己定持久化策略。 -模型阶段抛出未处理异常时,框架先运行 `save_state`,再跳过正常的渲染与分发。外层错误路径调用 `on_error(stage="turn", ...)` 后重新抛出异常。内置错误观察器可以通过出站分发发送错误响应。单个 `on_error` 观察器失败不会阻止其他观察器执行。 +模型阶段抛出未处理异常时,框架先跑 `save_state`,然后跳过渲染和分发。外层的错误路径调用 `on_error(stage="turn", ...)`,再把异常抛出去。内置的错误观察器可以通过出站分发发一条错误回复。某个 `on_error` 观察器失败,不影响其他观察器。 -## 扩展应用 +## 下一步 -按照[插件指南](/zh-cn/docs/app/plugins/)注册实现,通过 [Hooks 示例](/zh-cn/docs/app/hooks/)完成具体修改,精确签名和顺序见 [Hooks 参考](/zh-cn/docs/reference/hooks/)。 +用[插件设置](/zh-cn/docs/app/plugins/)注册你的实现。常见改法见[改变应用行为](/zh-cn/docs/app/hooks/)。精确的签名和顺序见[应用 Hooks](/zh-cn/docs/reference/hooks/)。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/acp.mdx b/website/src/content/docs/zh-cn/docs/integrations/acp.mdx index dca386c7c..d832d9fde 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/acp.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/acp.mdx @@ -130,5 +130,5 @@ HELLO ## 下一步 - [配置](/zh-cn/docs/app/configure/):Bub 的模型和运行时设置。 -- [应用运行流程](/zh-cn/docs/concepts/turn/):Bub 如何把入站消息转成模型调用和出站消息。 +- [轮次](/zh-cn/docs/concepts/turn/):Bub 如何把入站消息转成模型调用和出站消息。 - [构建插件](/zh-cn/docs/app/plugins/):用 hooks、tools 和渠道扩展 Bub。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/observability.mdx b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx index 8cdc42026..3ebae975e 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/observability.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx @@ -8,7 +8,7 @@ sidebar: 先查看 Bub 应用的会话历史,再按需导出执行轨迹,检查模型与工具调用的耗时。你可以从本地 Tape 命令开始,接入 Phoenix 或 Logfire 后查看一次运行中的多次调用。 -本指南使用应用的 CLI 和配置。会话记录与轨迹导出各有自己的生命周期:即使没有外部追踪服务,默认 Agent 仍会保存选定的对话与运行记录。[事件与调用控制](/zh-cn/docs/concepts/events/)说明了事件、输出流和持久化记录之间的区别。 +本指南使用应用的 CLI 和配置。会话记录与轨迹导出各有自己的生命周期:即使没有外部追踪服务,默认 Agent 仍会保存选定的对话与运行记录。[事件](/zh-cn/docs/concepts/events/)说明了事件、输出流和持久化记录之间的区别。 ## 前置条件 diff --git a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx index abd0d517b..30b4a5165 100644 --- a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx @@ -3,7 +3,7 @@ title: 应用 Hooks description: Bub 插件的 hook 签名、调用规则和资源生命周期。 --- -Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 个 hooks。用法示例见[改变应用行为](/zh-cn/docs/app/hooks/),应用阶段顺序见[应用的一轮处理](/zh-cn/docs/concepts/turn/)。 +Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 个 hooks。用法示例见[改变应用行为](/zh-cn/docs/app/hooks/),应用阶段顺序见[轮次](/zh-cn/docs/concepts/turn/)。 下文的“首个”指按 hook 优先级取得第一个非 `None` 的结果,“全部”指调用每个实现。普通 hook 抛出异常时,本次分发停止,异常向上传播;[agent 拦截 hooks](#agent-loop-hooks-pluginagenthooks) 和 `on_error` 有各自的异常处理规则。 diff --git a/website/src/content/docs/zh-cn/docs/reference/index.mdx b/website/src/content/docs/zh-cn/docs/reference/index.mdx index 933f67af0..6d8e543c4 100644 --- a/website/src/content/docs/zh-cn/docs/reference/index.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/index.mdx @@ -3,7 +3,7 @@ title: 参考 description: 查阅 Agent 接口、共享能力契约和 Bub 应用 API。 --- -按代码中用到的接口查阅。Agent 接口与应用扩展点各有职责;[公共概念与能力](/zh-cn/docs/concepts/)说明它们之间的关系。 +按代码中用到的接口查阅。Agent 接口与应用扩展点各有职责;[概览](/zh-cn/docs/concepts/)说明它们之间的关系。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx index af0c20e78..34fa4f770 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx @@ -82,7 +82,7 @@ async def remember(note: str, *, context: ToolContext) -> str: remember_tool = Tool.from_callable(remember, context=True) ``` -这会在 Tape 中写入一条业务事件。默认上下文选择器读取会话消息与工具交互;要把这条事件交给模型,需要提供自己的选择逻辑。哪些已保存的信息应交给模型,由[会话历史与上下文](/zh-cn/docs/concepts/tape/#context)中的选择规则决定。 +这会在 Tape 中写入一条业务事件。默认上下文选择器读取会话消息与工具交互;要把这条事件交给模型,需要提供自己的选择逻辑。哪些已保存的信息应交给模型,由[Tape](/zh-cn/docs/concepts/tape/#context)中的选择规则决定。 From befbee4130354c6184d39dbf91969215f093cfd6 Mon Sep 17 00:00:00 2001 From: 100gle Date: Wed, 21 Oct 2026 14:41:27 +0800 Subject: [PATCH 04/28] docs(app): organize the application section by what you do MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The section had two groups, "Run it" and "Extend it", that mixed running, extending, and shipping. Pages were named after the work ("Configure the app", "Change application behavior") instead of the thing they describe. - group the pages into Run, Extend, and Ship - name each page after its subject: Workspace, Configuration, Terminal, Channels, Tools, Hooks, Distribution - drop the app overview: the sidebar already lists every page - merge the Telegram page into Channels, which covers it as one section - rename first-session to workspace and remove the parts that quickstart already explains - fold the language-extension page into Plugins as "WASM plugins (experimental)"; it described one plugin, not a concept of its own - say what a channel covers, since a plugin can add another platform or a background runtime - use 技能 for skills, and keep Hooks and CodeMode in English because the code uses those names - add the Chinese redirects that were missing, and point 80 old URLs per locale at their new pages - keep the Vite dependency cache of build and dev apart, so a build no longer forces a running dev server to reload its pre-bundle --- website/astro.config.mjs | 116 +++++++++---- .../src/content/docs/docs/app/channels.mdx | 159 ++++++++++++++++++ .../src/content/docs/docs/app/configure.mdx | 6 +- website/src/content/docs/docs/app/deploy.mdx | 8 +- .../app/{package.mdx => distribution.mdx} | 4 +- .../content/docs/docs/app/first-session.mdx | 63 ------- website/src/content/docs/docs/app/gateway.mdx | 90 ---------- website/src/content/docs/docs/app/hooks.mdx | 4 +- website/src/content/docs/docs/app/index.mdx | 42 ----- .../docs/docs/app/language-extensions.mdx | 150 ----------------- website/src/content/docs/docs/app/plugins.mdx | 46 ++++- website/src/content/docs/docs/app/skills.mdx | 4 +- .../src/content/docs/docs/app/telegram.mdx | 94 ----------- .../docs/docs/app/{chat.mdx => terminal.mdx} | 6 +- website/src/content/docs/docs/app/tools.mdx | 8 +- .../src/content/docs/docs/app/workspace.mdx | 48 ++++++ .../src/content/docs/docs/concepts/events.mdx | 2 +- .../docs/docs/concepts/extension-points.mdx | 4 +- .../src/content/docs/docs/concepts/turn.mdx | 2 +- .../docs/docs/getting-started/index.mdx | 2 +- .../docs/docs/getting-started/install.mdx | 2 +- .../docs/docs/getting-started/quickstart.mdx | 4 +- .../content/docs/docs/integrations/index.mdx | 2 +- .../src/content/docs/docs/reference/cli.mdx | 2 +- .../src/content/docs/docs/reference/hooks.mdx | 2 +- .../content/docs/docs/reference/settings.mdx | 4 +- .../src/content/docs/docs/reference/types.mdx | 2 +- .../content/docs/docs/reference/versions.mdx | 4 +- .../src/content/docs/docs/sdk/app-plugins.mdx | 2 +- website/src/content/docs/docs/sdk/index.md | 2 +- .../content/docs/zh-cn/docs/app/channels.mdx | 159 ++++++++++++++++++ .../content/docs/zh-cn/docs/app/configure.mdx | 6 +- .../content/docs/zh-cn/docs/app/deploy.mdx | 10 +- .../app/{package.mdx => distribution.mdx} | 12 +- .../docs/zh-cn/docs/app/first-session.mdx | 63 ------- .../content/docs/zh-cn/docs/app/gateway.mdx | 90 ---------- .../src/content/docs/zh-cn/docs/app/hooks.mdx | 6 +- .../src/content/docs/zh-cn/docs/app/index.mdx | 42 ----- .../zh-cn/docs/app/language-extensions.mdx | 150 ----------------- .../content/docs/zh-cn/docs/app/plugins.mdx | 48 +++++- .../content/docs/zh-cn/docs/app/skills.mdx | 50 +++--- .../content/docs/zh-cn/docs/app/telegram.mdx | 94 ----------- .../zh-cn/docs/app/{chat.mdx => terminal.mdx} | 6 +- .../src/content/docs/zh-cn/docs/app/tools.mdx | 10 +- .../content/docs/zh-cn/docs/app/workspace.mdx | 48 ++++++ .../docs/zh-cn/docs/concepts/events.mdx | 2 +- .../zh-cn/docs/concepts/extension-points.mdx | 12 +- .../docs/zh-cn/docs/concepts/index.mdx | 8 +- .../content/docs/zh-cn/docs/concepts/turn.mdx | 4 +- .../docs/zh-cn/docs/getting-started/index.mdx | 6 +- .../zh-cn/docs/getting-started/install.mdx | 4 +- .../zh-cn/docs/getting-started/quickstart.mdx | 6 +- .../docs/zh-cn/docs/integrations/acp.mdx | 4 +- .../zh-cn/docs/integrations/agent-plugins.mdx | 22 +-- .../docs/zh-cn/docs/integrations/index.mdx | 4 +- .../zh-cn/docs/integrations/local-models.mdx | 2 +- .../docs/zh-cn/docs/integrations/mcp.mdx | 4 +- .../content/docs/zh-cn/docs/reference/cli.mdx | 2 +- .../docs/zh-cn/docs/reference/hooks.mdx | 2 +- .../docs/zh-cn/docs/reference/settings.mdx | 4 +- .../docs/zh-cn/docs/reference/types.mdx | 2 +- .../docs/zh-cn/docs/reference/versions.mdx | 8 +- .../docs/zh-cn/docs/sdk/app-plugins.mdx | 8 +- .../src/content/docs/zh-cn/docs/sdk/index.md | 4 +- 64 files changed, 728 insertions(+), 1058 deletions(-) create mode 100644 website/src/content/docs/docs/app/channels.mdx rename website/src/content/docs/docs/app/{package.mdx => distribution.mdx} (98%) delete mode 100644 website/src/content/docs/docs/app/first-session.mdx delete mode 100644 website/src/content/docs/docs/app/gateway.mdx delete mode 100644 website/src/content/docs/docs/app/index.mdx delete mode 100644 website/src/content/docs/docs/app/language-extensions.mdx delete mode 100644 website/src/content/docs/docs/app/telegram.mdx rename website/src/content/docs/docs/app/{chat.mdx => terminal.mdx} (89%) create mode 100644 website/src/content/docs/docs/app/workspace.mdx create mode 100644 website/src/content/docs/zh-cn/docs/app/channels.mdx rename website/src/content/docs/zh-cn/docs/app/{package.mdx => distribution.mdx} (92%) delete mode 100644 website/src/content/docs/zh-cn/docs/app/first-session.mdx delete mode 100644 website/src/content/docs/zh-cn/docs/app/gateway.mdx delete mode 100644 website/src/content/docs/zh-cn/docs/app/index.mdx delete mode 100644 website/src/content/docs/zh-cn/docs/app/language-extensions.mdx delete mode 100644 website/src/content/docs/zh-cn/docs/app/telegram.mdx rename website/src/content/docs/zh-cn/docs/app/{chat.mdx => terminal.mdx} (93%) create mode 100644 website/src/content/docs/zh-cn/docs/app/workspace.mdx diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 5a22cb455..6a32e2ea1 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -30,38 +30,85 @@ export default defineConfig({ // Documentation moved in the 0.6 documentation restructure. Keep old URLs working. redirects: { '/docs': '/docs/getting-started/', - '/docs/getting-started/first-turn': '/docs/app/first-session/', - '/docs/getting-started/first-skill': '/docs/app/skills/', - '/docs/getting-started/first-plugin': '/docs/app/plugins/', - '/docs/build': '/docs/app/', - '/docs/build/sdk': '/docs/sdk/', - '/docs/build/tools': '/docs/sdk/tools/', + '/docs/app': '/docs/app/workspace/', + '/docs/app/chat': '/docs/app/terminal/', + '/docs/app/first-session': '/docs/app/workspace/', + '/docs/app/gateway': '/docs/app/channels/', + '/docs/app/language-extensions': '/docs/app/plugins/', + '/docs/app/package': '/docs/app/distribution/', + '/docs/app/telegram': '/docs/app/channels/', + '/docs/build': '/docs/app/workspace/', '/docs/build/app-tools': '/docs/app/tools/', - '/docs/build/skills': '/docs/app/skills/', - '/docs/build/plugins': '/docs/app/plugins/', + '/docs/build/distribution': '/docs/app/distribution/', '/docs/build/hooks': '/docs/app/hooks/', + '/docs/build/plugins': '/docs/app/plugins/', '/docs/build/reuse-plugins': '/docs/sdk/app-plugins/', - '/docs/build/distribution': '/docs/app/package/', - '/docs/concepts/philosophy': '/docs/concepts/', + '/docs/build/sdk': '/docs/sdk/', + '/docs/build/skills': '/docs/app/skills/', + '/docs/build/tools': '/docs/sdk/tools/', '/docs/concepts/design': '/docs/concepts/', + '/docs/concepts/philosophy': '/docs/concepts/', '/docs/concepts/surfaces': '/docs/concepts/extension-points/', '/docs/concepts/tape-and-context': '/docs/concepts/tape/', '/docs/concepts/turn-pipeline': '/docs/concepts/turn/', - '/docs/operate': '/docs/app/', + '/docs/getting-started/first-plugin': '/docs/app/plugins/', + '/docs/getting-started/first-skill': '/docs/app/skills/', + '/docs/getting-started/first-turn': '/docs/app/workspace/', + '/docs/operate': '/docs/app/workspace/', + '/docs/operate/channels': '/docs/app/channels/', + '/docs/operate/channels/cli': '/docs/app/terminal/', + '/docs/operate/channels/telegram': '/docs/app/channels/', '/docs/operate/configure': '/docs/app/configure/', - '/docs/operate/channels': '/docs/app/gateway/', - '/docs/operate/channels/cli': '/docs/app/chat/', - '/docs/operate/channels/telegram': '/docs/app/telegram/', '/docs/operate/deploy': '/docs/app/deploy/', '/docs/tutorials': '/docs/integrations/', - '/docs/tutorials/mcp': '/docs/integrations/mcp/', '/docs/tutorials/acp-server': '/docs/integrations/acp/', '/docs/tutorials/agent-plugins': '/docs/integrations/agent-plugins/', '/docs/tutorials/fastapi': '/docs/sdk/web-service/', '/docs/tutorials/language-extensions': '/docs/app/language-extensions/', '/docs/tutorials/local-llama-cpp': '/docs/integrations/local-models/', - '/docs/tutorials/tapestore-sqlalchemy': '/docs/integrations/storage/', + '/docs/tutorials/mcp': '/docs/integrations/mcp/', '/docs/tutorials/observability': '/docs/integrations/observability/', + '/docs/tutorials/tapestore-sqlalchemy': '/docs/integrations/storage/', + '/zh-cn/docs': '/zh-cn/docs/getting-started/', + '/zh-cn/docs/app': '/zh-cn/docs/app/workspace/', + '/zh-cn/docs/app/chat': '/zh-cn/docs/app/terminal/', + '/zh-cn/docs/app/first-session': '/zh-cn/docs/app/workspace/', + '/zh-cn/docs/app/gateway': '/zh-cn/docs/app/channels/', + '/zh-cn/docs/app/language-extensions': '/zh-cn/docs/app/plugins/', + '/zh-cn/docs/app/package': '/zh-cn/docs/app/distribution/', + '/zh-cn/docs/app/telegram': '/zh-cn/docs/app/channels/', + '/zh-cn/docs/build': '/zh-cn/docs/app/workspace/', + '/zh-cn/docs/build/app-tools': '/zh-cn/docs/app/tools/', + '/zh-cn/docs/build/distribution': '/zh-cn/docs/app/distribution/', + '/zh-cn/docs/build/hooks': '/zh-cn/docs/app/hooks/', + '/zh-cn/docs/build/plugins': '/zh-cn/docs/app/plugins/', + '/zh-cn/docs/build/reuse-plugins': '/zh-cn/docs/sdk/app-plugins/', + '/zh-cn/docs/build/sdk': '/zh-cn/docs/sdk/', + '/zh-cn/docs/build/skills': '/zh-cn/docs/app/skills/', + '/zh-cn/docs/build/tools': '/zh-cn/docs/sdk/tools/', + '/zh-cn/docs/concepts/design': '/zh-cn/docs/concepts/', + '/zh-cn/docs/concepts/philosophy': '/zh-cn/docs/concepts/', + '/zh-cn/docs/concepts/surfaces': '/zh-cn/docs/concepts/extension-points/', + '/zh-cn/docs/concepts/tape-and-context': '/zh-cn/docs/concepts/tape/', + '/zh-cn/docs/concepts/turn-pipeline': '/zh-cn/docs/concepts/turn/', + '/zh-cn/docs/getting-started/first-plugin': '/zh-cn/docs/app/plugins/', + '/zh-cn/docs/getting-started/first-skill': '/zh-cn/docs/app/skills/', + '/zh-cn/docs/getting-started/first-turn': '/zh-cn/docs/app/workspace/', + '/zh-cn/docs/operate': '/zh-cn/docs/app/workspace/', + '/zh-cn/docs/operate/channels': '/zh-cn/docs/app/channels/', + '/zh-cn/docs/operate/channels/cli': '/zh-cn/docs/app/terminal/', + '/zh-cn/docs/operate/channels/telegram': '/zh-cn/docs/app/channels/', + '/zh-cn/docs/operate/configure': '/zh-cn/docs/app/configure/', + '/zh-cn/docs/operate/deploy': '/zh-cn/docs/app/deploy/', + '/zh-cn/docs/tutorials': '/zh-cn/docs/integrations/', + '/zh-cn/docs/tutorials/acp-server': '/zh-cn/docs/integrations/acp/', + '/zh-cn/docs/tutorials/agent-plugins': '/zh-cn/docs/integrations/agent-plugins/', + '/zh-cn/docs/tutorials/fastapi': '/zh-cn/docs/sdk/web-service/', + '/zh-cn/docs/tutorials/language-extensions': '/zh-cn/docs/app/language-extensions/', + '/zh-cn/docs/tutorials/local-llama-cpp': '/zh-cn/docs/integrations/local-models/', + '/zh-cn/docs/tutorials/mcp': '/zh-cn/docs/integrations/mcp/', + '/zh-cn/docs/tutorials/observability': '/zh-cn/docs/integrations/observability/', + '/zh-cn/docs/tutorials/tapestore-sqlalchemy': '/zh-cn/docs/integrations/storage/', }, env: { schema: { @@ -75,6 +122,13 @@ export default defineConfig({ }, }, vite: { + // Keep the dependency cache of `astro build` away from the one `astro dev` + // uses. When both share `node_modules/.vite`, every build rewrites the + // optimized deps and a running dev server answers with "There is a new + // version of the pre-bundle" instead of hot-reloading. + cacheDir: + process.env.VITE_CACHE_DIR ?? + (astro_image_mode === 'dev' ? 'node_modules/.vite' : 'node_modules/.vite-build'), plugins: [tailwindcss()], resolve: { alias: { @@ -166,31 +220,35 @@ export default defineConfig({ label: 'Use Bub App', translations: { 'zh-CN': '使用 Bub 应用' }, items: [ - 'docs/app', { - label: 'Run it', - translations: { 'zh-CN': '运行与部署' }, + label: 'Run', + translations: { 'zh-CN': '运行' }, collapsed: false, items: [ - 'docs/app/first-session', + 'docs/app/workspace', 'docs/app/configure', - 'docs/app/chat', - 'docs/app/gateway', - 'docs/app/telegram', - 'docs/app/deploy', - 'docs/app/package', + 'docs/app/terminal', + 'docs/app/channels', ], }, { - label: 'Extend it', - translations: { 'zh-CN': '扩展它' }, - collapsed: true, + label: 'Extend', + translations: { 'zh-CN': '扩展' }, + collapsed: false, items: [ 'docs/app/tools', 'docs/app/skills', 'docs/app/plugins', 'docs/app/hooks', - 'docs/app/language-extensions', + ], + }, + { + label: 'Ship', + translations: { 'zh-CN': '上线' }, + collapsed: false, + items: [ + 'docs/app/deploy', + 'docs/app/distribution', ], }, ], diff --git a/website/src/content/docs/docs/app/channels.mdx b/website/src/content/docs/docs/app/channels.mdx new file mode 100644 index 000000000..114b299d6 --- /dev/null +++ b/website/src/content/docs/docs/app/channels.mdx @@ -0,0 +1,159 @@ +--- +title: Channels +description: Choose the channels the gateway starts, tune batching and session ids, and configure the Telegram bot. +sidebar: + order: 3 +--- + +A channel is how messages enter and leave the Bub app. `cli` is the terminal, `telegram` is a bot on Telegram, and a plugin can add another messaging platform or a background runtime. The gateway starts the enabled channels, batches their messages, and keys their sessions. The application framework manages channels. A service that uses an Agent directly reads its own input and writes its own output. + +## Before you begin + +- You have run `bub onboard`, or you wrote `~/.bub/config.yml` yourself. See [Configuration](/docs/app/configure/). +- You have a Telegram bot token from [@BotFather](https://t.me/BotFather), if you want to use Telegram. +- For group chats, **Privacy Mode is off** in BotFather. With it on, the bot sees commands only, and the inbound filter cannot decide whether to answer. + +## Choose channels + +`bub gateway` listens on every enabled channel that has a working config. By default it enables `all`, which expands to every enabled channel that is not an `Interface`. That set includes listeners such as Telegram and `Lifecycle` runtimes such as `mcp.lifecycle`. The builtin `cli` interface is excluded, so a long-running gateway does not take the terminal of the operator. + +Enable one channel: + +```bash +bub gateway --enable-channel telegram +``` + +Enable several: + +```bash +bub gateway --enable-channel telegram --enable-channel wechat +``` + +Exclude a default lifecycle channel: + +```bash +bub gateway --enable-channel all --enable-channel '!mcp.lifecycle' +``` + +To pin the default in the config, set `enabled_channels` (comma-separated, or `all`): + +```yaml +enabled_channels: telegram +``` + +Or use the environment variable: + +```bash +BUB_ENABLED_CHANNELS=telegram +``` + +A channel starts only if its `enabled` property returns `True`. The Telegram channel, for example, returns `False` when `BUB_TELEGRAM_TOKEN` is empty, so the gateway skips it without an error. + +## Debounce and batching + +A channel that is not the CLI can combine a burst of short messages into one turn. These variables control the debounce: + +| Variable | Default | Meaning | +|---|---|---| +| `BUB_DEBOUNCE_SECONDS` | `1.0` | Quiet time after an active message before the batch is flushed. | +| `BUB_MAX_WAIT_SECONDS` | `10.0` | Hard upper bound on how long a follow-up message can extend the wait. | +| `BUB_ACTIVE_TIME_WINDOW` | `60.0` | Window in which other messages still count as part of the active conversation. | + +Bub stores the pending messages of each session, and it merges them into one `ChannelMessage` when the timer fires. + +:::tip +Comma commands (input that starts with `,`) bypass the debounce. They are dispatched the moment they arrive. This is useful for `,quit`, `,help`, or a tool name. +::: + +## Session ids + +Bub keys conversation state by `session_id`. The default rule is `:`: + +- `bub run "..."` (no `--session-id`) gives `cli:local`. +- A Telegram inbound message gives `telegram:`. +- The `bub chat` REPL gives `cli_session`, a literal string that does not follow the pattern. + +Use `--session-id` on `bub run` or `bub chat` to share state across surfaces. + +## Builtin channels + +| Channel | On by default | Debounce | +|---|---|---| +| `cli` | only through `bub chat` | no | +| `telegram` | yes, when `BUB_TELEGRAM_TOKEN` is set | yes | + +A plugin can register more channels through the `provide_channels` hook. See [Plugins](/docs/app/plugins/). + +## Telegram + +The Telegram channel is registered as `telegram`. It uses long polling through `python-telegram-bot`. It turns on at runtime when `BUB_TELEGRAM_TOKEN` is set. + +### Set the token + +```bash +BUB_TELEGRAM_TOKEN=123456:abcdef... +``` + +This one variable is the switch. When it is empty, the channel reports `enabled=False`, and `bub gateway` skips it without an error. + +### Restrict who can use the bot + +The allowlists are comma-separated, and usernames are case-sensitive: + +```bash +BUB_TELEGRAM_ALLOW_USERS=123456789,your_username +BUB_TELEGRAM_ALLOW_CHATS=-1001234567890,-1009876543210 +``` + +The two lists behave in different ways: + +- With `BUB_TELEGRAM_ALLOW_CHATS` set, messages from any other chat are dropped silently. +- With `BUB_TELEGRAM_ALLOW_USERS` set, other users receive `Access denied.` +- A user matches when either the numeric id or the username is in the list. + +:::caution +If both lists are empty, anyone who finds the bot can drive it. For a production deployment, set at least one. +::: + +### Route through a proxy + +```bash +BUB_TELEGRAM_PROXY=http://127.0.0.1:7890 +# or socks5://user:pass@host:port +``` + +The same proxy serves the polling connection and the outbound API calls. + +### Start the gateway + +```bash +bub gateway --enable-channel telegram +``` + +`--enable-channel telegram` pins the listener to one channel. Without it, `bub gateway` enables every non-`Interface` channel in the default set, which includes Telegram when its token is set, and it also starts the enabled `Lifecycle` runtimes. On startup you should see: + +```text +telegram.start allow_users_count=2 allow_chats_count=1 proxy_enabled=False +telegram.start polling +``` + +### Inbound messages + +The channel uses `telegram:` as the session id. A private chat and a group chat stay separate, even when the same user is in both. + +- `/start` is handled internally. The bot replies with a fixed greeting, or with the "not allowed" message in a chat that is not in the allowlist. +- `/bub ` is normalized. The `/bub ` prefix is removed, and the rest becomes the prompt. +- In a private chat, every message that is not a command is ingested. +- In a group chat, every message that is not a command reaches the channel. A message is active when it mentions `@`, contains the keyword `bub`, or replies to a message from the bot. Other group messages are ignored, unless a conversation for that session is open within `BUB_ACTIVE_TIME_WINDOW`. In that case they join the batch. + +### Outbound messages + +A normal inbound message sets `output_channel="null"` before it enters the turn pipeline. A plain model answer is therefore not sent back blindly. The bundled `telegram` skill is the normal path for replies, progress updates, and edits. + +Comma commands are different. They keep `output_channel="telegram"`, so their tool output goes back to the originating `chat_id`. When the content is JSON with a `message` field, only that field is sent. Otherwise the raw content is sent. An empty payload is dropped. + +## Next steps + +- [Terminal](/docs/app/terminal/): the REPL and one-shot turns. +- [Deployment](/docs/app/deploy/): run the bot in a container with persistent state. +- [Settings reference](/docs/reference/settings/): every channel variable. diff --git a/website/src/content/docs/docs/app/configure.mdx b/website/src/content/docs/docs/app/configure.mdx index d35c16a3d..912b25e49 100644 --- a/website/src/content/docs/docs/app/configure.mdx +++ b/website/src/content/docs/docs/app/configure.mdx @@ -1,5 +1,5 @@ --- -title: Configure the app +title: Configuration description: Set the Bub application's model, provider credentials, and channels. sidebar: order: 1 @@ -79,7 +79,7 @@ BUB_TELEGRAM_ALLOW_CHATS=-1001234567890 BUB_TELEGRAM_PROXY=http://127.0.0.1:7890 # optional ``` -For the message-handling and access-control behavior these variables drive, see [Telegram](/docs/app/telegram/). +For the message-handling and access-control behavior these variables drive, see [Channels](/docs/app/channels/#telegram). ## 5. Verify @@ -93,6 +93,6 @@ The command prints a routing label (`[:]`) followed by the res ## Next steps -- [Channels](/docs/app/gateway/): choose which surfaces Bub listens on. +- [Channels](/docs/app/channels/): choose which surfaces Bub listens on. - [Settings reference](/docs/reference/settings/): full env-var table. - [Deploy](/docs/app/deploy/): package this config into a container. diff --git a/website/src/content/docs/docs/app/deploy.mdx b/website/src/content/docs/docs/app/deploy.mdx index d1b383831..0d762b0c5 100644 --- a/website/src/content/docs/docs/app/deploy.mdx +++ b/website/src/content/docs/docs/app/deploy.mdx @@ -1,8 +1,8 @@ --- -title: Deploy the app +title: Deployment description: Run the Bub application with Docker Compose and keep its data across restarts. sidebar: - order: 3 + order: 10 --- Run the Bub application in a container, mount its data on the host, and choose the gateway channels it starts. Download and unpack the source archive for Bub 0.6.0 from [GitHub Releases](https://github.com/bubbuild/bub/releases), then run the commands below from that directory. @@ -13,7 +13,7 @@ The repository ships three files that drive the canonical deployment: [`Dockerfi - Docker (or compatible runtime) and `docker compose` are installed. - You have a `.env` with at minimum `BUB_MODEL` and either `BUB_API_KEY` or a provider-specific key: see [Configure](/docs/app/configure/). -- For a Telegram deployment, `BUB_TELEGRAM_TOKEN` and an allowlist are in `.env`: see [Telegram](/docs/app/telegram/). +- For a Telegram deployment, `BUB_TELEGRAM_TOKEN` and an allowlist are in `.env`: see [Channels](/docs/app/channels/#telegram). ## 1. Prepare the env file @@ -140,5 +140,5 @@ Both commands operate on the managed project at `BUB_PROJECT` (default `bub.home ## Next steps - [Configure](/docs/app/configure/): env-var precedence and provider settings. -- [Telegram](/docs/app/telegram/): bot token and allowlist behavior. +- [Channels](/docs/app/channels/#telegram): bot token and allowlist behavior. - [CLI reference](/docs/reference/cli/): flags for `bub gateway`, `bub install`, `bub update`. diff --git a/website/src/content/docs/docs/app/package.mdx b/website/src/content/docs/docs/app/distribution.mdx similarity index 98% rename from website/src/content/docs/docs/app/package.mdx rename to website/src/content/docs/docs/app/distribution.mdx index 4a0dd83d4..ca042ba97 100644 --- a/website/src/content/docs/docs/app/package.mdx +++ b/website/src/content/docs/docs/app/distribution.mdx @@ -1,8 +1,8 @@ --- -title: Package a Bub application +title: Distribution description: Embed the application framework or ship a CLI with your own plugins and skills. sidebar: - order: 5 + order: 11 --- Build on the Bub app when you want its configuration, plugins, and message routing in your product. You can host `BubFramework` in an existing process or ship a command that starts it. diff --git a/website/src/content/docs/docs/app/first-session.mdx b/website/src/content/docs/docs/app/first-session.mdx deleted file mode 100644 index 6b4953dc6..000000000 --- a/website/src/content/docs/docs/app/first-session.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Run Bub for the first time -description: Give Bub a workspace, add your instructions, and complete a conversation. -sidebar: - order: 2 ---- - -Start with the Bub app, then adapt it to your work. This tutorial gives it a workspace and instructions, checks that commands work, and runs a conversation with a model. - -Follow [Install Bub](/docs/getting-started/install/) for the application setup and choose a model in [Configure Bub](/docs/app/configure/). The model-backed step needs the credentials for that provider. The command check works without them. - -## Give Bub a workspace - -The workspace is the directory Bub uses for your project. Create one and run the remaining commands there: - -```bash -mkdir hello-bub -cd hello-bub -``` - -Put your working instructions in `AGENTS.md`: - -```bash -cat > AGENTS.md <<'EOF' -You are the Bub agent for this workspace. -Read files before editing them. -Keep replies short and specific. -EOF -``` - -The application reads this file when it builds the system prompt. You can change it as you learn which instructions help with your work. - -## Check a command - -```bash -bub run ",help" -``` - -The output lists commands such as `,skill` and `,tape.info`. This checks the application's command path without calling a model. - -Commands begin with a comma at the start of a line. An unknown command falls back to a shell command, so use a known command for this check. - -## Start a conversation - -With your model configured, run: - -```bash -bub run "Summarize the purpose of this workspace." -``` - -Bub passes the message and workspace instructions to its agent, records the conversation, and prints the response. For an interactive conversation, use: - -```bash -bub chat -``` - -If a command works but a conversation fails, check the model name and credentials in [Configure Bub](/docs/app/configure/). If a plugin is missing, `bub hooks` shows which hook implementations the application discovered. - -## Make it your own - -- Add a [project skill](/docs/app/skills/) for a procedure you want Bub to follow. -- Add an [application tool](/docs/app/tools/) when it needs to call your code. -- Write a [plugin](/docs/app/plugins/) to change application behavior. diff --git a/website/src/content/docs/docs/app/gateway.mdx b/website/src/content/docs/docs/app/gateway.mdx deleted file mode 100644 index 019a2b40c..000000000 --- a/website/src/content/docs/docs/app/gateway.mdx +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: Connect channels -description: Choose application channels, configure message batching, and name sessions. -sidebar: - order: 2 ---- - -Channels connect the Bub application to terminals and messaging services. Choose which channels the gateway starts, how incoming messages are batched, and how sessions are named. The application framework manages channels; a service using Agent directly handles its own input and output. - -## Before you begin - -- You have run `bub onboard` or written `~/.bub/config.yml` directly. See [Configure](/docs/app/configure/). -- For each non-CLI channel, the channel's credentials are set (e.g. `BUB_TELEGRAM_TOKEN`). - -## 1. Choose which channels to enable - -`bub gateway` listens on every enabled channel that ships with a working config. By default it enables `all`, which expands to every enabled non-`Interface` channel. That includes listener channels such as Telegram as well as `Lifecycle` runtimes such as `mcp.lifecycle`. The built-in `cli` interface is excluded so that a long-lived gateway does not steal the operator's terminal. - -Enable a specific channel: - -```bash -bub gateway --enable-channel telegram -``` - -Enable several: - -```bash -bub gateway --enable-channel telegram --enable-channel wechat -``` - -Exclude a default lifecycle channel: - -```bash -bub gateway --enable-channel all --enable-channel '!mcp.lifecycle' -``` - -To pin the default in config, set `enabled_channels` (comma-separated, or `all`): - -```yaml -enabled_channels: telegram -``` - -Or via env var: - -```bash -BUB_ENABLED_CHANNELS=telegram -``` - -A channel is only started if its `enabled` property returns `True`. The Telegram channel, for example, returns `False` when `BUB_TELEGRAM_TOKEN` is empty, so it is skipped without error. - -## 2. Tune debounce and batching - -Non-CLI channels can combine a burst of short messages into one turn. Configure this debounce behavior with: - -| Variable | Default | Meaning | -|---|---|---| -| `BUB_DEBOUNCE_SECONDS` | `1.0` | Quiet time required after an active message before the batch is flushed. | -| `BUB_MAX_WAIT_SECONDS` | `10.0` | Hard upper bound on how long a follow-up message can extend the wait. | -| `BUB_ACTIVE_TIME_WINDOW` | `60.0` | Window during which non-active messages still count as part of the active conversation. | - -Bub stores each session's pending messages and merges them into one `ChannelMessage` when the timer fires. - -:::tip -Comma commands (input starting with `,`) bypass debounce entirely. They are dispatched the moment they arrive; useful for `,quit`, `,help`, or any tool name. -::: - -## 3. Understand session ids - -Bub keys conversation state by `session_id`. The default rule is `:`: - -- `bub run "..."` (no `--session-id`) → `cli:local` -- Telegram inbound → `telegram:` -- `bub chat` REPL → `cli_session` (literal string, not the `:` pattern) - -Override with `--session-id` on `bub run` or `bub chat` when you need to share state across surfaces. - -## 4. Builtin channels - -| Channel | Default? | Debounces? | Page | -|---|---|---|---| -| `cli` | only via `bub chat` | no | [CLI](/docs/app/chat/) | -| `telegram` | yes (when `BUB_TELEGRAM_TOKEN` is set) | yes | [Telegram](/docs/app/telegram/) | - -Plugins can register additional channels through the `provide_channels` hook. Use [plugins](/docs/app/plugins/) to add other channel adapters. - -## Next steps - -- [CLI](/docs/app/chat/): REPL and one-shot turns. -- [Telegram](/docs/app/telegram/): token, allowlists, group behavior. -- [Settings reference](/docs/reference/settings/): every channel variable. diff --git a/website/src/content/docs/docs/app/hooks.mdx b/website/src/content/docs/docs/app/hooks.mdx index 727d43465..d0c86fa4c 100644 --- a/website/src/content/docs/docs/app/hooks.mdx +++ b/website/src/content/docs/docs/app/hooks.mdx @@ -1,8 +1,8 @@ --- -title: Change application behavior +title: Hooks description: Use hooks to shape prompts, control calls, save application state, and provide resources. sidebar: - order: 2 + order: 8 --- Choose a hook at the point where you want to change the Bub app. Keep the rest of the application in place: a prompt hook changes the input, a tool decision controls one call, and a store provider changes where conversation history lives. diff --git a/website/src/content/docs/docs/app/index.mdx b/website/src/content/docs/docs/app/index.mdx deleted file mode 100644 index 6754ef02f..000000000 --- a/website/src/content/docs/docs/app/index.mdx +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Use the Bub app -description: Run the Bub application, configure it for your work, and choose what to add. -sidebar: - order: 0 ---- - -Start with the running Bub application and add the behavior your work needs. The builtin `BubAgent` extends `core.Agent`; the Framework supplies application orchestration, plugin hooks, and channel routing. - -These guides use the Bub application's configuration and extension points. To supply your own model and tools directly in a Python program, start with [Build your own Bub Agent](/docs/sdk/). - -## Run the app - -Follow the [application installation](/docs/getting-started/install/#app-install) and [run your first conversation](/docs/app/first-session/). Then [configure the model and workspace](/docs/app/configure/) for your work. - -Day to day, choose how Bub reaches you: - -- [Use the terminal](/docs/app/chat/): run one task or continue a conversation. -- [Connect Telegram](/docs/app/telegram/): set up a bot and choose who can use it. -- [Run a gateway](/docs/app/gateway/): keep every enabled channel listening in one process. -- [Deploy the app](/docs/app/deploy/): build the container and keep its data across restarts. - -## Choose what to add - -| Your task | Start here | -| --- | --- | -| Add a callable function for the model | [Tools in the Bub app](/docs/app/tools/) | -| Give the model reusable instructions | [Skills](/docs/app/skills/) | -| Package an integration or replace an application hook | [Plugins](/docs/app/plugins/) and [Hooks](/docs/app/hooks/) | -| Accept messages through CLI, Telegram, or another adapter | [Channels](/docs/app/gateway/) | -| Ship a distribution | [Package a Bub application](/docs/app/package/) | -| Keep a Bub service running | [Deployment](/docs/app/deploy/) | - -Tools, conversation history, and agent events keep their [shared contracts](/docs/concepts/). The app adds its own defaults, including tool loading and code mode selection. [Tools in the Bub app](/docs/app/tools/) explains those policies; [one turn](/docs/concepts/turn/) explains how the Framework connects incoming messages to outgoing responses. - -## Reuse an integration - -- Browse [Bub Hub](https://hub.bub.build) for plugins, skills, and distributions. -- Follow the [integration guides](/docs/integrations/) to connect a service or protocol. -- Check the [application reference](/docs/reference/#application) for signatures and defaults. - -For a service that owns a `core.Agent` directly, follow [Serve an agent with FastAPI](/docs/sdk/web-service/). That route manages models and storage in Python. diff --git a/website/src/content/docs/docs/app/language-extensions.mdx b/website/src/content/docs/docs/app/language-extensions.mdx deleted file mode 100644 index 2c7ffe500..000000000 --- a/website/src/content/docs/docs/app/language-extensions.mdx +++ /dev/null @@ -1,150 +0,0 @@ ---- -title: Implement hooks in another language -description: Use the experimental Extism plugin to implement selected application hooks in WebAssembly. -sidebar: - badge: { text: 'Experimental', variant: 'caution' } - order: 5 ---- - -Implement selected Bub application hooks in Go, Rust, or another language through the experimental `bub-extism` plugin. Bub runs the Python application; Extism loads the WebAssembly modules that implement your hooks. - -This guide connects two example modules. Check the plugin's supported hooks and value conversions before choosing this integration for your application. - -## Before you start - -Make sure you have: - -- The [Bub application](/docs/getting-started/install/#app-install), with `bub --help` working. -- A Go toolchain if you plan to build the Go example. -- A Rust toolchain with the `wasm32-unknown-unknown` target if you plan to build the Rust example. - -The plugin documentation covers the supported hooks and module format: - -- [`bub-extism` README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) -- [`bub-extism` examples](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism/examples) -- [Extism manifest reference](https://extism.org/docs/concepts/manifest/) -- [Bub hooks reference](/docs/reference/hooks/) - -## 1. Install the experimental plugin - -Install `bub-extism` into the same environment where Bub runs: - -```bash -bub install bub-extism@main -``` - -Verify that Bub picks it up: - -```bash -bub hooks -``` - -You should see `extism` listed alongside `builtin`. - -## 2. Implement one hook in Go and one in Rust - -Use the two example modules shipped with `bub-extism`: - -- `go-build-prompt` implements the `build_prompt` hook -- `rust-run-model` implements the `run_model` hook - -Build the Go example: - -```bash -cd bub-contrib/packages/bub-extism/examples/go-build-prompt -GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o go-build-prompt.wasm . -``` - -Build the Rust example: - -```bash -cd bub-contrib/packages/bub-extism/examples/rust-run-model -cargo build --release --target wasm32-unknown-unknown -``` - -If you need the full build commands for either example, check the [examples guide](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism/examples). - -## 3. Wire both modules into `extism.json` - -Create an `extism.json` in your project root that binds the prompt hook to the Go module and the model hook to the Rust module: - -```json -{ - "plugins": { - "prompt": { - "manifest": { - "wasm": [ - { - "path": "bub-contrib/packages/bub-extism/examples/go-build-prompt/go-build-prompt.wasm" - } - ] - }, - "wasi": true, - "hooks": { - "build_prompt": "build_prompt" - } - }, - "model": { - "manifest": { - "wasm": [ - { - "path": "bub-contrib/packages/bub-extism/examples/rust-run-model/target/wasm32-unknown-unknown/release/bub_extism_rust_run_model.wasm" - } - ] - }, - "hooks": { - "run_model": "run_model" - } - } - } -} -``` - -The configuration connects the modules to the application: - -- `manifest` follows the standard Extism manifest format. -- `hooks` maps Bub hook names to wasm exports. -- Bub still owns dispatch and precedence. - -Point Bub at the config file before running a turn: - -```bash -export BUB_EXTISM_CONFIG_PATH=./extism.json -``` - -## 4. Run a turn that spans both languages - -Now let Bub build a prompt first, then execute the model hook: - -```bash -bub run "Say hello from a prompt built outside Python." -``` - -When everything is wired up correctly, Bub will: - -1. Call `build_prompt` inside the Go module -2. Pass the returned prompt to `run_model` inside the Rust module -3. Hand the final result back for this turn - -The CLI prints outbound messages in `[channel:chat_id]` format, so a default `bub run` typically shows `[cli:local]` first, followed by the rendered output. - -You can expect output similar to: - -```text -[cli:local] -[rust-run-model:cli:local] [go-build-prompt:cli:local] Say hello from a prompt built outside Python. -``` - -The Go module wraps the inbound prompt with a `[go-build-prompt:]` prefix; the Rust module then wraps the Go output with `[rust-run-model:]`. The leading `[cli:local]` line is the channel header emitted by the CLI adapter. - -## 5. Check the integration boundary - -The Go module builds the prompt and the Rust module formats a response from it. These examples verify hook loading and data exchange. To use the builtin agent's model calls and conversation recording, keep its `run_model` implementation. - -Check that the plugin supports the hooks you need, and verify the arguments and return values passed between Python and WebAssembly. - -## Next steps - -- [Building plugins](/docs/app/plugins/): package a Python integration for the Bub application. -- [`bub-extism` README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism): read the package conventions and supported hooks. -- [Extism overview](https://extism.org/docs/overview): understand how Extism runs wasm modules across languages. diff --git a/website/src/content/docs/docs/app/plugins.mdx b/website/src/content/docs/docs/app/plugins.mdx index c6521683f..b0581e1f3 100644 --- a/website/src/content/docs/docs/app/plugins.mdx +++ b/website/src/content/docs/docs/app/plugins.mdx @@ -2,10 +2,10 @@ title: Plugins description: Write a plugin, choose its entry point, and install it into the app. sidebar: - order: 0 + order: 7 --- -A plugin changes how the Bub app handles a task. This page builds a small echo plugin so you can check discovery and turn processing without model credentials, then covers the entry-point choices, tool registration, and sharing. +A plugin changes how the Bub app handles a task. This page builds a small echo plugin so you can check discovery and turn processing without model credentials, then covers the entry-point choices, tool registration, sharing, and the WebAssembly route for other languages. A Python package declares an entry point in the `bub` group; Bub imports that target and registers its hook implementations. You can also reuse an existing plugin in your own service while keeping your host's entry point; see [Borrow the app runtime](/docs/sdk/app-plugins/). @@ -143,7 +143,7 @@ class MyPlugin: return f"Describe the workspace at {self.framework.workspace}." ``` -These examples replace the prompt. Choose the hook for your actual task from [Change application behavior](/docs/app/hooks/). +These examples replace the prompt. Choose the hook for your actual task from [Hooks](/docs/app/hooks/). ## Register tools during import @@ -153,10 +153,46 @@ If the package defines `@tool` functions, import their module from the entry-poi from . import tools # noqa: F401 ``` -The application takes a snapshot of registered tools when it creates a `BubAgent`. See [Add application tools](/docs/app/tools/) for registration, deferred loading, and code mode. +The application takes a snapshot of registered tools when it creates a `BubAgent`. See [Tools](/docs/app/tools/) for registration, deferred loading, and code mode. ## Share the plugin -Build and install a wheel into a separate environment before publishing it. Confirm that the entry point loads and that a representative task uses your hook. If you bundle skills or several plugins, follow [Package a Bub application](/docs/app/package/). +Build and install a wheel into a separate environment before publishing it. Confirm that the entry point loads and that a representative task uses your hook. If you bundle skills or several plugins, follow [Distribution](/docs/app/distribution/). Portable Agent Plugins use a different format: `plugin.json`, skills, and MCP servers. The [Agent Plugins integration](/docs/integrations/agent-plugins/) loads those packages through a Python plugin. + + + +## WASM plugins (experimental) + +A plugin does not have to be Python. The [`bub-extism`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) plugin runs WebAssembly modules and maps their exports to hooks. Bub still dispatches the hooks and decides precedence; Extism only calls your module. + +Install it into the environment where Bub runs: + +```bash +bub install bub-extism@main +bub hooks +``` + +`extism.json` names each module and the hook it implements. `manifest` uses the Extism format, and `hooks` maps a Bub hook to a wasm export: + +```json +{ + "plugins": { + "prompt": { + "manifest": { "wasm": [{ "path": "go-build-prompt.wasm" }] }, + "wasi": true, + "hooks": { "build_prompt": "build_prompt" } + } + } +} +``` + +```bash +export BUB_EXTISM_CONFIG_PATH=./extism.json +bub run "Say hello from a prompt built outside Python." +``` + +The [README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) lists the hooks this route supports and how arguments convert. Check both before you depend on it: not every hook is available, and every value crosses a Python-to-wasm boundary. The [examples](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism/examples) build one module in Go and one in Rust. + +Keep the builtin implementation for model calls and tape recording. A module suits hooks that do small work on their own. diff --git a/website/src/content/docs/docs/app/skills.mdx b/website/src/content/docs/docs/app/skills.mdx index b6d1ed3fd..3b14ce9f2 100644 --- a/website/src/content/docs/docs/app/skills.mdx +++ b/website/src/content/docs/docs/app/skills.mdx @@ -2,7 +2,7 @@ title: Skills description: Write a reusable procedure, check that the app loads it, and package it for other users. sidebar: - order: 0 + order: 6 --- A skill gives the Bub app a procedure it can load for a task. This page walks through a project skill, then covers the file fields, discovery order, and packaging. Keep project-specific skills in the workspace, or include them in a Python package so other users can install them. @@ -125,4 +125,4 @@ bub run ",skill name=my-skill" If it returns `(no such skill)`, check the directory name, frontmatter, and wheel contents. If it prints an unexpected version, look for a project or user skill with the same name. -To distribute a CLI and plugins alongside these skills, see [Package a Bub application](/docs/app/package/). +To distribute a CLI and plugins alongside these skills, see [Distribution](/docs/app/distribution/). diff --git a/website/src/content/docs/docs/app/telegram.mdx b/website/src/content/docs/docs/app/telegram.mdx deleted file mode 100644 index 1453f5323..000000000 --- a/website/src/content/docs/docs/app/telegram.mdx +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Connect Telegram -description: Configure the builtin Telegram bot; token, allowlists, session ids, debounce behavior, and how to launch the gateway. ---- - -Connect the Bub application to a Telegram bot, choose who can use it, and start the gateway. - -The Telegram channel is registered under the name `telegram`. It uses long polling via `python-telegram-bot`, and it is enabled at runtime when `BUB_TELEGRAM_TOKEN` is set. - -## Before you begin - -- A bot exists in [@BotFather](https://t.me/BotFather) and you have its token. -- For group chats, **Privacy Mode is disabled** in BotFather. Otherwise the bot only sees commands, and the inbound filter cannot decide whether to respond. -- Bub is configured with a working model: see [Configure](/docs/app/configure/). - -## 1. Set the token - -```bash -BUB_TELEGRAM_TOKEN=123456:abcdef... -``` - -This single variable is the on/off switch. With it unset, the channel reports `enabled=False` and `bub gateway` skips it without error. - -## 2. Restrict who can talk to the bot - -Allowlists are comma-separated and case-sensitive on usernames: - -```bash -BUB_TELEGRAM_ALLOW_USERS=123456789,your_username -BUB_TELEGRAM_ALLOW_CHATS=-1001234567890,-1009876543210 -``` - -Behavior: - -- If `BUB_TELEGRAM_ALLOW_CHATS` is set, messages from any other chat are dropped silently. -- If `BUB_TELEGRAM_ALLOW_USERS` is set, messages from any other user receive `Access denied.` -- A user matches when **either** their numeric id or their username appears in the list. - -:::caution -Leaving both allowlists empty means anyone who finds the bot can drive it. For any production deployment, set at least one. -::: - -## 3. Optional: route through a proxy - -```bash -BUB_TELEGRAM_PROXY=http://127.0.0.1:7890 -# or socks5://user:pass@host:port -``` - -The same proxy is used for both the polling connection and outbound API calls. - -## 4. Launch the gateway - -```bash -bub gateway --enable-channel telegram -``` - -`--enable-channel telegram` pins the listener to one channel. Without it, `bub gateway` enables every non-`Interface` channel in the default runtime set, which includes Telegram when its token is set and also starts enabled `Lifecycle` runtimes. On startup you should see: - -```text -telegram.start allow_users_count=2 allow_chats_count=1 proxy_enabled=False -telegram.start polling -``` - -## 5. How inbound messages are framed - -The Telegram channel uses `telegram:` as the session id. That keeps a private chat and a group chat separate even if the same user is in both. - -Inbound handling: - -- `/start` is handled internally; the bot replies with a fixed greeting (or the "not allowed" message in disallowed chats). -- `/bub ` is normalized: the `/bub ` prefix is stripped and the rest becomes the prompt content. -- In private chats, every non-command message is ingested. -- In group chats, every non-command message reaches the Telegram channel. Messages that mention `@`, contain the keyword `bub`, or reply to one of the bot's own messages are marked active. Other group messages are ignored unless an active conversation for the same session is open within `BUB_ACTIVE_TIME_WINDOW`, in which case they join that batch. - -## 6. Debounce - -The Telegram channel sets `needs_debounce=True`. Inbound messages are batched per session using `BUB_DEBOUNCE_SECONDS`, `BUB_MAX_WAIT_SECONDS`, and `BUB_ACTIVE_TIME_WINDOW`. See [Channels overview](/docs/app/gateway/) for the values. - -:::tip -Comma commands (`,help`, `,fs.read ...`) always bypass debounce. They are dispatched the moment they arrive, regardless of any pending batch. -::: - -## 7. Outbound behavior - -Normal Telegram inbound messages set `output_channel="null"` before they enter the turn pipeline. That prevents a plain model return value from being sent back blindly. The bundled `telegram` skill is the canonical path for replies, progress updates, and edits. - -Comma commands are different: they keep `output_channel="telegram"`, so their tool output is sent back to the originating `chat_id`. If content routed to `TelegramChannel.send(...)` is JSON with a `"message"` field, only that field is sent; otherwise the raw content is sent. Empty payloads are dropped. - -## Next steps - -- [Channels overview](/docs/app/gateway/): debounce, `enabled_channels`, session ids. -- [Deploy](/docs/app/deploy/): package the bot into a container with persistent state. -- [Settings reference](/docs/reference/settings/): every `BUB_TELEGRAM_*` variable. diff --git a/website/src/content/docs/docs/app/chat.mdx b/website/src/content/docs/docs/app/terminal.mdx similarity index 89% rename from website/src/content/docs/docs/app/chat.mdx rename to website/src/content/docs/docs/app/terminal.mdx index 472e20b6e..0c5139d28 100644 --- a/website/src/content/docs/docs/app/chat.mdx +++ b/website/src/content/docs/docs/app/terminal.mdx @@ -1,6 +1,8 @@ --- -title: Use the terminal +title: Terminal description: Continue a conversation with bub chat or run one task from the shell with bub run. +sidebar: + order: 2 --- The Bub app has two terminal entry points: `bub chat` for a conversation and `bub run` for one message. [Configure a model](/docs/app/configure/) before calling it. @@ -41,4 +43,4 @@ bub run ",fs.read path=README.md" You can enter these commands in an interactive conversation too. Ctrl-X toggles shell mode, which adds the command prefix to your input. -The CLI owns the foreground terminal, so the gateway excludes it by default. See [Channels](/docs/app/gateway/) for gateway selection and batching, or the [CLI reference](/docs/reference/cli/) for every option. +The CLI owns the foreground terminal, so the gateway excludes it by default. See [Channels](/docs/app/channels/) for gateway selection and batching, or the [CLI reference](/docs/reference/cli/) for every option. diff --git a/website/src/content/docs/docs/app/tools.mdx b/website/src/content/docs/docs/app/tools.mdx index 0ccab5f08..df23c88f8 100644 --- a/website/src/content/docs/docs/app/tools.mdx +++ b/website/src/content/docs/docs/app/tools.mdx @@ -1,11 +1,11 @@ --- -title: Add application tools +title: Tools description: Register tools for BubAgent and choose how the app makes them available. sidebar: - order: 3 + order: 5 --- -Give the Bub app an action it can perform for your work. The [shared tools guide](/docs/sdk/tools/) explains function schemas, results, and tool context. This page covers application registration and the policies applied by `BubAgent`. +Give the Bub app an action it can perform for your work. The [Tools and CodeMode](/docs/sdk/tools/) page explains function schemas, results, and tool context. This page covers application registration and the policies applied by `BubAgent`. ## Register a tool @@ -99,7 +99,7 @@ The app connects CodeMode to the session's environment and model settings. Code Builtin `bash`, `bash.output`, `bash.kill`, `fs.*`, and `run_code` use the session's `Environment`. The default is `LocalEnvironment`, which runs on the host. -Provide another environment through a plugin's `provide_environment(session_id, workspace)` hook. The framework caches one environment per session and closes it when `framework.running()` exits. The [shared tools guide](/docs/sdk/tools/#run-tools-in-an-environment) describes the environment interface. +Provide another environment through a plugin's `provide_environment(session_id, workspace)` hook. The framework caches one environment per session and closes it when `framework.running()` exits. [Tools and CodeMode](/docs/sdk/tools/#run-tools-in-an-environment) describes the environment interface. A custom application tool can access that environment with `environment_from_state(context.state)` from `bub.builtin.environment`. The Python tool runs on the host and explicitly delegates operations to the environment when needed. diff --git a/website/src/content/docs/docs/app/workspace.mdx b/website/src/content/docs/docs/app/workspace.mdx new file mode 100644 index 000000000..10999ac10 --- /dev/null +++ b/website/src/content/docs/docs/app/workspace.mdx @@ -0,0 +1,48 @@ +--- +title: Workspace +description: Create a workspace, add your instructions, and check that commands work. +sidebar: + order: 0 +--- + +Bub works in a directory: the workspace. It reads and writes files there, and it takes the project instructions from there. + +This page creates a workspace, adds your instructions, and checks that the app's commands run. The check does not need model credentials. + +## Create a workspace + +Make a directory for your project, and run the remaining commands in it: + +```bash +mkdir hello-bub +cd hello-bub +``` + +Put your working instructions in `AGENTS.md`: + +```bash +cat > AGENTS.md <<'EOF' +You are the Bub agent for this workspace. +Read files before editing them. +Keep replies short and specific. +EOF +``` + +The application reads this file when it builds the system prompt. Change it as you learn which instructions help with your work. + +## Check a command + +```bash +bub run ",help" +``` + +The output lists commands such as `,skill` and `,tape.info`. This checks the command path of the app without a model call. + +Commands begin with a comma at the start of a line. An unknown command falls back to a shell command, so use a known command for this check. + +## Next steps + +- Add a [project skill](/docs/app/skills/) for a procedure that you want Bub to follow. +- Add a [tool](/docs/app/tools/) when Bub must call your code. +- Write a [plugin](/docs/app/plugins/) to change application behavior. +- Run a conversation in the [terminal](/docs/app/terminal/), or [connect a channel](/docs/app/channels/). diff --git a/website/src/content/docs/docs/concepts/events.mdx b/website/src/content/docs/docs/concepts/events.mdx index c8d20bd99..db1af7168 100644 --- a/website/src/content/docs/docs/concepts/events.mdx +++ b/website/src/content/docs/docs/concepts/events.mdx @@ -132,6 +132,6 @@ async def mark_reviewed() -> None: This adapter follows the plugin hook order. It catches exceptions from individual interception hooks, logs them, and skips them. To deny a call, return the correct decision. The other app hooks follow their own error contracts. -See [Change application behavior](/docs/app/hooks/) for plugin examples. For order and signatures, see [Application hooks](/docs/reference/hooks/). EventBus is part of the base SDK installation. +See [Hooks](/docs/app/hooks/) for plugin examples. For order and signatures, see [Application hooks](/docs/reference/hooks/). EventBus is part of the base SDK installation. `framework.events` publishes commands, channel work, and user prompts. It uses `CommandChanged`, `WorkChanged`, and `PromptChanged` from `bub.application`. You subscribe and unsubscribe with the same `on()` and `off()` API. diff --git a/website/src/content/docs/docs/concepts/extension-points.mdx b/website/src/content/docs/docs/concepts/extension-points.mdx index b997ae18d..7de6cef75 100644 --- a/website/src/content/docs/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/docs/concepts/extension-points.mdx @@ -14,13 +14,13 @@ Start with the behavior that you want to add. A new action needs a tool. A new p | Change which history becomes model messages | A [TapeContext](/docs/concepts/tape/#context). | Pass it to Agent, or provide it through an app hook. | | Teach a procedure that the agent can reuse | A [skill](/docs/app/skills/). | The Bub app discovers it and loads it. | | Change the app setup or the message processing | A [plugin](/docs/app/plugins/). | `BubFramework` and its hooks. | -| Connect a messaging service | A [channel](/docs/app/gateway/). | The inbound and outbound routing of the app. | +| Connect a messaging service | A [channel](/docs/app/channels/). | The inbound and outbound routing of the app. | ## Tools and skills A tool runs Python and returns a result. A skill is a directory with a `SKILL.md` file and, optionally, more files. Its instructions can tell the agent how to use tools. You write the Python functions that the instructions refer to, and you add them separately. -A plain Agent uses the tools and prompt sections that you give it. The Bub app discovers skills and reads `AGENTS.md`. See [Skills](/docs/app/skills/) and [Application tools](/docs/app/tools/). +A plain Agent uses the tools and prompt sections that you give it. The Bub app discovers skills and reads `AGENTS.md`. See [Skills](/docs/app/skills/) and [Tools](/docs/app/tools/). The agent can also reach a tool through a [text command](/docs/sdk/tools/#commands), or through Python that the model writes with CodeMode. All these paths use the same tool implementation. diff --git a/website/src/content/docs/docs/concepts/turn.mdx b/website/src/content/docs/docs/concepts/turn.mdx index f25d55c0e..ad290626b 100644 --- a/website/src/content/docs/docs/concepts/turn.mdx +++ b/website/src/content/docs/docs/concepts/turn.mdx @@ -45,4 +45,4 @@ If the model stage raises an unhandled exception, the framework runs `save_state ## Next steps -Use [plugin setup](/docs/app/plugins/) to register your implementation. For common changes, see [Change application behavior](/docs/app/hooks/). For exact signatures and ordering, see [Application hooks](/docs/reference/hooks/). +Use [plugin setup](/docs/app/plugins/) to register your implementation. For common changes, see [Hooks](/docs/app/hooks/). For exact signatures and ordering, see [Application hooks](/docs/reference/hooks/). diff --git a/website/src/content/docs/docs/getting-started/index.mdx b/website/src/content/docs/docs/getting-started/index.mdx index 058b357e4..60959a19a 100644 --- a/website/src/content/docs/docs/getting-started/index.mdx +++ b/website/src/content/docs/docs/getting-started/index.mdx @@ -62,7 +62,7 @@ A few rules keep the loop small: The rest of the documentation, in reading order: - **[Concepts](/docs/concepts/)**: tape, turns, events, extension points. Read these when a term in the guides is unclear; nothing else depends on them. -- **[Use the Bub app](/docs/app/)**: configure it, then add tools, skills, plugins, hooks, and channels. +- **[Use the Bub app](/docs/app/workspace/)**: configure it, then add tools, skills, plugins, hooks, and channels. - **[Build your own agent](/docs/sdk/)**: keep the kernel and write the loop's surroundings yourself. - **[Integrations](/docs/integrations/)**: MCP, ACP, agent plugins, local models, SQLite, and tracing. - **[Reference](/docs/reference/)**: CLI commands, settings, types, hooks, and version history. diff --git a/website/src/content/docs/docs/getting-started/install.mdx b/website/src/content/docs/docs/getting-started/install.mdx index 6d275711f..44bf027ad 100644 --- a/website/src/content/docs/docs/getting-started/install.mdx +++ b/website/src/content/docs/docs/getting-started/install.mdx @@ -50,7 +50,7 @@ When installation finishes, make sure the command directory is on your PATH, the bub --help ``` -Continue with [Run Bub for the first time](/docs/app/first-session/) to start a conversation in your own workspace. +Continue with [Workspace](/docs/app/workspace/) to create a workspace and add your project instructions. The base install and `bub[app]` contain the same package files. The extra changes which dependencies are installed. Without the application dependencies, the `bub` command exits with an instruction to install `bub[app]`. diff --git a/website/src/content/docs/docs/getting-started/quickstart.mdx b/website/src/content/docs/docs/getting-started/quickstart.mdx index 8d685379f..31febc7c3 100644 --- a/website/src/content/docs/docs/getting-started/quickstart.mdx +++ b/website/src/content/docs/docs/getting-started/quickstart.mdx @@ -35,7 +35,7 @@ Ask it something: Bub reads `AGENTS.md` when the directory has one, records the turn on its tape, and answers with the model you configured. Press `Ctrl+C` to leave. -Next: [your first session](/docs/app/first-session/) covers workspaces, commands, and session names. +Next: [Workspace](/docs/app/workspace/) covers `AGENTS.md` and the commands of the app. @@ -86,7 +86,7 @@ Next: [Build your own Bub Agent](/docs/sdk/) adds a tool and explains the stream =0.6.0` in place of a plain `bub` dependency. The extra installs the dependencies required by the builtin runtime, Framework, settings, plugin hooks, skills, and channels. -Run `bub --help`, then [run a conversation](/docs/app/first-session/). Use `bub hooks` when checking that your plugins loaded. +Run `bub --help`, then [run a conversation in the terminal](/docs/app/terminal/). Use `bub hooks` when checking that your plugins loaded. ## Choose the interfaces your code uses | Your code | Dependency and guide | | --- | --- | | Supplies a model and tools directly to `core.Agent` | `bub`; [Build your own agent](/docs/sdk/) | -| Uses `BubAgent`, the Framework, application hooks, or channels | `bub[app]`; [Customize the Bub app](/docs/app/) | +| Uses `BubAgent`, the Framework, application hooks, or channels | `bub[app]`; [Use the Bub app](/docs/app/workspace/) | A directly composed Agent takes its model and components in Python. The Bub application supplies its settings, skills, and tool policies through `BubAgent` and the Framework. [Tools](/docs/sdk/tools/) and [Agent events](/docs/concepts/events/) explain the shared contracts and the differences in default behavior. diff --git a/website/src/content/docs/docs/sdk/app-plugins.mdx b/website/src/content/docs/docs/sdk/app-plugins.mdx index 1db8d3ae9..9b8211c10 100644 --- a/website/src/content/docs/docs/sdk/app-plugins.mdx +++ b/website/src/content/docs/docs/sdk/app-plugins.mdx @@ -147,7 +147,7 @@ The plugin's implementation determines what your host must provide: | --- | --- | | Tools or a store | Import the public component and pass it to Agent. For example, `bub_tapestore_sqlalchemy.store.SQLAlchemyTapeStore` accepts a database URL and can be passed as `store=`. | | Model or tool interception, system prompt, continuation, or steering | Register the selected plugin and [attach `AgentPlugins`](#agent-plugins). Check any state fields or tools the plugin expects. | -| Message preparation, model-stage replacement, state saving, or delivery | [Embed the application pipeline](/docs/app/package/#programmatic-embedding) in your host. It runs these stages and returns a `TurnResult`. | +| Message preparation, model-stage replacement, state saving, or delivery | [Embed the application pipeline](/docs/app/distribution/#programmatic-embedding) in your host. It runs these stages and returns a `TurnResult`. | | Background connections or discovered tools | Open the service, wait until it is ready, attach its tools, then close it with the host. Follow the plugin's own lifecycle API. | Follow the chosen store's lifecycle contract. `framework.running()` acquires hook-provided resources and closes the context managers it entered. A store returned as an ordinary object keeps its own cleanup requirements. diff --git a/website/src/content/docs/docs/sdk/index.md b/website/src/content/docs/docs/sdk/index.md index ec5d3df7b..9a6b4d752 100644 --- a/website/src/content/docs/docs/sdk/index.md +++ b/website/src/content/docs/docs/sdk/index.md @@ -7,7 +7,7 @@ sidebar: Choose a model, add tools, and connect the agent to your program. `bub.core.Agent` runs the model and tool loop while you decide how to store conversations and manage the surrounding application. -You supply the agent's components directly. The [Bub application](/docs/app/) builds on the same core and adds configuration and plugin discovery. You can also [reuse application plugins](/docs/sdk/app-plugins/) in your own service while keeping its existing entry points and calling code. +You supply the agent's components directly. The [Bub application](/docs/app/workspace/) builds on the same core and adds configuration and plugin discovery. You can also [reuse application plugins](/docs/sdk/app-plugins/) in your own service while keeping its existing entry points and calling code. ## Install diff --git a/website/src/content/docs/zh-cn/docs/app/channels.mdx b/website/src/content/docs/zh-cn/docs/app/channels.mdx new file mode 100644 index 000000000..82606f044 --- /dev/null +++ b/website/src/content/docs/zh-cn/docs/app/channels.mdx @@ -0,0 +1,159 @@ +--- +title: 渠道 +description: 选择网关启动哪些渠道,设置消息合并与会话 ID,并配置 Telegram 机器人。 +sidebar: + order: 3 +--- + +渠道是消息进出 Bub 应用的方式。`cli` 是终端,`telegram` 是 Telegram 上的机器人,插件还能再加一个聊天平台或后台运行时。网关负责启动已启用的渠道、合并它们的消息、给它们的会话命名。渠道由应用框架管理。直接用 Agent 的服务,自己读输入,自己写输出。 + +## 开始之前 + +- 你已运行 `bub onboard`,或者自己写了 `~/.bub/config.yml`。见[配置](/zh-cn/docs/app/configure/)。 +- 要用 Telegram 的话,已从 [@BotFather](https://t.me/BotFather) 拿到机器人 token。 +- 群聊使用时,BotFather 里的 **Privacy Mode 是关闭的**。开着的话机器人只看得到命令,入站过滤器没法判断要不要回复。 + +## 选择渠道 + +`bub gateway` 监听所有已启用、配置又齐全的渠道。默认启用 `all`,会展开成所有已启用、且不是 `Interface` 的渠道。这个集合里既有 Telegram 这类监听渠道,也有 `mcp.lifecycle` 这类 `Lifecycle` 后台服务。内置的 `cli` 接口被排除在外,所以长期运行的网关不会占着运维者的终端。 + +启用一个渠道: + +```bash +bub gateway --enable-channel telegram +``` + +启用多个: + +```bash +bub gateway --enable-channel telegram --enable-channel wechat +``` + +排除默认的后台渠道: + +```bash +bub gateway --enable-channel all --enable-channel '!mcp.lifecycle' +``` + +要在配置里固定默认值,设 `enabled_channels`(逗号分隔,或 `all`): + +```yaml +enabled_channels: telegram +``` + +或者用环境变量: + +```bash +BUB_ENABLED_CHANNELS=telegram +``` + +只有渠道的 `enabled` 属性返回 `True` 时它才会启动。比如 `BUB_TELEGRAM_TOKEN` 为空时,Telegram 渠道返回 `False`,网关直接跳过,不报错。 + +## 防抖与批处理 + +非 CLI 的渠道可以把一串短消息合成一轮。这三个变量控制防抖: + +| 变量 | 默认值 | 含义 | +|---|---|---| +| `BUB_DEBOUNCE_SECONDS` | `1.0` | 一条活跃消息之后要保持安静多久,才把这一批发出去。 | +| `BUB_MAX_WAIT_SECONDS` | `10.0` | 后续消息能延长等待的硬上限。 | +| `BUB_ACTIVE_TIME_WINDOW` | `60.0` | 在这段时间内,其他消息仍算作当前对话的一部分。 | + +Bub 按会话暂存待发的消息,计时到了就合成一条 `ChannelMessage`。 + +:::tip +逗号命令(以 `,` 开头的输入)不走防抖。它们一到就分发。`,quit`、`,help`、工具名都属于这类。 +::: + +## 会话 ID + +Bub 用 `session_id` 索引会话状态。默认规则是 `:`: + +- `bub run "..."`(不带 `--session-id`)→ `cli:local` +- Telegram 收到的消息 → `telegram:` +- `bub chat` 的 REPL → `cli_session`,一个固定的字符串,不遵循上面的格式 + +要在多个接入面之间共享状态,给 `bub run` 或 `bub chat` 加 `--session-id`。 + +## 内置渠道 + +| 渠道 | 默认启用 | 防抖 | +|---|---|---| +| `cli` | 只在 `bub chat` 里 | 否 | +| `telegram` | 是,前提是设了 `BUB_TELEGRAM_TOKEN` | 是 | + +插件可以通过 `provide_channels` hook 注册更多渠道。见[插件](/zh-cn/docs/app/plugins/)。 + +## Telegram + +Telegram 渠道的注册名是 `telegram`。它用 `python-telegram-bot` 做长轮询。设了 `BUB_TELEGRAM_TOKEN`,它就在运行时打开。 + +### 设置 token + +```bash +BUB_TELEGRAM_TOKEN=123456:abcdef... +``` + +这一个变量就是开关。留空时,渠道报告 `enabled=False`,`bub gateway` 跳过它,不报错。 + +### 限制谁能用 + +白名单用逗号分隔,用户名区分大小写: + +```bash +BUB_TELEGRAM_ALLOW_USERS=123456789,your_username +BUB_TELEGRAM_ALLOW_CHATS=-1001234567890,-1009876543210 +``` + +两个名单的行为不一样: + +- 设了 `BUB_TELEGRAM_ALLOW_CHATS`,其他聊天的消息静默丢弃。 +- 设了 `BUB_TELEGRAM_ALLOW_USERS`,其他用户会收到 `Access denied.`。 +- 数字 id 或用户名命中任意一个,就算通过。 + +:::caution +两个名单都空着,等于谁找到机器人都能驱动它。生产环境至少要设一个。 +::: + +### 走代理 + +```bash +BUB_TELEGRAM_PROXY=http://127.0.0.1:7890 +# 或 socks5://user:pass@host:port +``` + +轮询连接和出站 API 调用用的是同一个代理。 + +### 启动网关 + +```bash +bub gateway --enable-channel telegram +``` + +`--enable-channel telegram` 把监听器固定在一个渠道上。不带它时,`bub gateway` 会启用默认集合里所有非 `Interface` 的渠道,token 已设时包含 Telegram,同时也会启动已启用的 `Lifecycle` 服务。启动时应该看到: + +```text +telegram.start allow_users_count=2 allow_chats_count=1 proxy_enabled=False +telegram.start polling +``` + +### 入站消息 + +这个渠道用 `telegram:` 作为会话 ID。同一个用户既在私聊又在群里,两边的会话也是分开的。 + +- `/start` 由内部处理。机器人回一句固定欢迎语;不在白名单的聊天里,回的是"不允许"。 +- `/bub ` 会被规范化。去掉 `/bub ` 前缀,剩下的当作 prompt。 +- 私聊里,所有非命令消息都会被收下。 +- 群聊里,所有非命令消息都会到渠道。提到 `@`、带关键字 `bub`、或者回复机器人的消息,算作活跃。其他群消息被忽略,除非这个会话在 `BUB_ACTIVE_TIME_WINDOW` 内还有对话开着,那它们就并进这一批。 + +### 出站消息 + +普通消息进轮次流水线之前会被设成 `output_channel="null"`。所以模型随口一句的回答不会被直接发回 Telegram。回复、进度更新、改消息,走的是内置的 `telegram` 技能。 + +逗号命令不一样。它们保留 `output_channel="telegram"`,工具输出会发回原来的 `chat_id`。内容是带 `message` 字段的 JSON 时,只发这个字段;否则发原始内容。空载荷直接丢掉。 + +## 下一步 + +- [终端](/zh-cn/docs/app/terminal/):REPL 和单次轮次。 +- [部署](/zh-cn/docs/app/deploy/):把机器人放进容器,状态持久化。 +- [设置参考](/zh-cn/docs/reference/settings/):全部渠道变量。 diff --git a/website/src/content/docs/zh-cn/docs/app/configure.mdx b/website/src/content/docs/zh-cn/docs/app/configure.mdx index b0d3ad693..8d5a0987f 100644 --- a/website/src/content/docs/zh-cn/docs/app/configure.mdx +++ b/website/src/content/docs/zh-cn/docs/app/configure.mdx @@ -1,5 +1,5 @@ --- -title: 配置应用 +title: 配置 description: 设置 Bub 应用的模型、提供商凭据和渠道。 sidebar: order: 1 @@ -81,7 +81,7 @@ BUB_TELEGRAM_ALLOW_CHATS=-1001234567890 BUB_TELEGRAM_PROXY=http://127.0.0.1:7890 # 可选 ``` -这些变量驱动的消息处理与访问控制行为见 [Telegram](/zh-cn/docs/app/telegram/)。 +这些变量驱动的消息处理与访问控制行为见 [渠道](/zh-cn/docs/app/channels/#telegram)。 ## 5. 验证 @@ -95,6 +95,6 @@ bub run "say hi" ## 后续阅读 -- [渠道](/zh-cn/docs/app/gateway/):选择 Bub 监听的接入面。 +- [渠道](/zh-cn/docs/app/channels/):选择 Bub 监听的接入面。 - [设置参考](/zh-cn/docs/reference/settings/):完整环境变量表。 - [部署](/zh-cn/docs/app/deploy/):把这份配置打入容器。 diff --git a/website/src/content/docs/zh-cn/docs/app/deploy.mdx b/website/src/content/docs/zh-cn/docs/app/deploy.mdx index fccc0e994..520ad386e 100644 --- a/website/src/content/docs/zh-cn/docs/app/deploy.mdx +++ b/website/src/content/docs/zh-cn/docs/app/deploy.mdx @@ -1,8 +1,8 @@ --- -title: 部署应用 +title: 部署 description: 用 Docker Compose 运行 Bub 应用,并在重启后保留数据。 sidebar: - order: 3 + order: 10 --- 把 Bub 应用放进容器,将数据挂载到宿主机,并选择网关启动的渠道。从 [GitHub Releases](https://github.com/bubbuild/bub/releases) 获取 Bub 0.6.0 的源码归档并解压,在解压后的目录中运行以下命令。 @@ -13,7 +13,7 @@ sidebar: - 已安装 Docker(或兼容运行时)与 `docker compose`。 - `.env` 中至少包含 `BUB_MODEL` 以及 `BUB_API_KEY` 或某个提供商专用 key:参见 [配置](/zh-cn/docs/app/configure/)。 -- Telegram 部署还需要 `.env` 中包含 `BUB_TELEGRAM_TOKEN` 与一份白名单:参见 [Telegram](/zh-cn/docs/app/telegram/)。 +- Telegram 部署还需要 `.env` 中包含 `BUB_TELEGRAM_TOKEN` 与一份白名单:参见 [渠道](/zh-cn/docs/app/channels/#telegram)。 ## 1. 准备 env 文件 @@ -45,7 +45,7 @@ volumes: |---|---| | `/workspace` | Bub 视为工作仓库的目录。默认是你执行 `docker compose` 时的目录。 | | `/data` | Bub 数据目录的宿主机绑定(命令历史、Tape、插件项目)。 | -| `/root/.agents` | 共享 skill 目录(沿用 `agents.md` 约定)。 | +| `/root/.agents` | 共享技能目录(沿用 `agents.md` 约定)。 | Compose 文件会在容器内设置 `BUB_HOME=/data`。如果要把宿主机侧源路径放到 `~/.bub` 以外的位置,请设置 `BUB_HOME_HOST`。此部署的运行时设置应放在 `.env` 中;`BUB_HOME` 不会移动 Bub 默认的 `config.yml` 路径。 @@ -140,5 +140,5 @@ docker compose exec app bub update specific-plugin # 升级指定插件 ## 后续阅读 - [配置](/zh-cn/docs/app/configure/):环境变量优先级与提供商设置。 -- [Telegram](/zh-cn/docs/app/telegram/):bot token 与白名单行为。 +- [渠道](/zh-cn/docs/app/channels/#telegram):bot token 与白名单行为。 - [CLI 参考](/zh-cn/docs/reference/cli/):`bub gateway`、`bub install`、`bub update` 的参数。 diff --git a/website/src/content/docs/zh-cn/docs/app/package.mdx b/website/src/content/docs/zh-cn/docs/app/distribution.mdx similarity index 92% rename from website/src/content/docs/zh-cn/docs/app/package.mdx rename to website/src/content/docs/zh-cn/docs/app/distribution.mdx index a64fa57e6..2312f6c9b 100644 --- a/website/src/content/docs/zh-cn/docs/app/package.mdx +++ b/website/src/content/docs/zh-cn/docs/app/distribution.mdx @@ -1,8 +1,8 @@ --- -title: 打包 Bub 应用 -description: 嵌入应用框架,或发布包含自有插件和 skills 的 CLI。 +title: 分发 +description: 嵌入应用框架,或发布包含自有插件和技能的 CLI。 sidebar: - order: 5 + order: 11 --- 如果产品需要 Bub 的配置、插件和消息路由,可以在 Bub 应用上继续构建。你可以把 `BubFramework` 放进现有进程,也可以发布一个启动它的命令。 @@ -74,7 +74,7 @@ my-bub = "my_bub.cli:main" 声明 `bub[app]>=0.6.0` 应用依赖。更新已有发行包时,按[升级到 0.6.0](/zh-cn/docs/reference/versions/)调整。 -## 一起分发插件和 skills +## 一起分发插件和技能 一个 wheel 可以提供多个入口点: @@ -87,6 +87,6 @@ channels = "my_bub.channels:ChannelPlugin" packages = ["src/my_bub", "src/skills"] ``` -每个目标都遵循[插件注册规则](/zh-cn/docs/app/plugins/)。任务指令按 [skills 包目录结构](/zh-cn/docs/app/skills/#ship-skills-with-your-package)加入发行包。 +每个目标都遵循[插件注册规则](/zh-cn/docs/app/plugins/)。任务指令按 [技能包目录结构](/zh-cn/docs/app/skills/#ship-skills-with-your-package)加入发行包。 -发布前,把构建好的 wheel 安装到独立环境,运行命令、核对插件报告、加载打包的 skill,再完成一次有代表性的对话。对于宿主应用,还应检查流或后台工具运行期间的关闭行为。 +发布前,把构建好的 wheel 安装到独立环境,运行命令、核对插件报告、加载打包的技能,再完成一次有代表性的对话。对于宿主应用,还应检查流或后台工具运行期间的关闭行为。 diff --git a/website/src/content/docs/zh-cn/docs/app/first-session.mdx b/website/src/content/docs/zh-cn/docs/app/first-session.mdx deleted file mode 100644 index bf9154c62..000000000 --- a/website/src/content/docs/zh-cn/docs/app/first-session.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: 首次运行 Bub -description: 为 Bub 准备工作区、添加指令,完成一次对话。 -sidebar: - order: 2 ---- - -从现成的 Bub 应用开始,再让它适应你的工作。这篇教程会准备工作区和指令,检查命令是否可用,然后与模型完成一次对话。 - -先按[安装 Bub](/zh-cn/docs/getting-started/install/)完成应用安装,再按[配置 Bub](/zh-cn/docs/app/configure/)选择模型。对话步骤需要对应供应商的凭据;检查命令时不需要。 - -## 准备工作区 - -工作区是 Bub 处理当前项目的目录。创建目录后,在这里运行后续命令: - -```bash -mkdir hello-bub -cd hello-bub -``` - -把工作指令写入 `AGENTS.md`: - -```bash -cat > AGENTS.md <<'EOF' -你是这个工作区的 Bub agent。 -编辑文件前先阅读相关内容。 -回复保持简短、具体。 -EOF -``` - -应用构建系统提示词时会读取这个文件。随着使用,你可以调整其中的指令,让它更符合自己的工作习惯。 - -## 检查命令 - -```bash -bub run ",help" -``` - -输出会列出 `,skill`、`,tape.info` 等命令。这一步检查应用的命令路径,不会调用模型。 - -命令以行首的逗号开头。未知命令会回退为 shell 命令,因此检查时请使用这里的已知命令。 - -## 开始对话 - -配置好模型后,运行: - -```bash -bub run "概括这个工作区的用途。" -``` - -Bub 把消息和工作区指令交给 agent,记录会话历史,并打印回复。需要交互式对话时,运行: - -```bash -bub chat -``` - -如果命令可用而对话失败,按[配置 Bub](/zh-cn/docs/app/configure/)检查模型名称和凭据。如果某个插件未生效,运行 `bub hooks` 查看应用发现了哪些 hook 实现。 - -## 按你的需要定制 - -- 添加[项目 skill](/zh-cn/docs/app/skills/),让 Bub 遵循常用流程。 -- 添加[应用工具](/zh-cn/docs/app/tools/),让 Bub 调用自己的代码。 -- 编写[插件](/zh-cn/docs/app/plugins/),改变应用行为。 diff --git a/website/src/content/docs/zh-cn/docs/app/gateway.mdx b/website/src/content/docs/zh-cn/docs/app/gateway.mdx deleted file mode 100644 index 12f47aa23..000000000 --- a/website/src/content/docs/zh-cn/docs/app/gateway.mdx +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: 接入渠道 -description: 选择应用渠道、设置消息合并,并管理会话 ID。 -sidebar: - order: 2 ---- - -渠道把终端或聊天服务接入 Bub 应用。本页说明如何选择网关启动的渠道、合并短时间内收到的消息,以及确定会话 ID。渠道由应用框架管理;直接使用 Agent 的服务自行接收和发送消息。 - -## 开始之前 - -- 你已运行 `bub onboard` 或直接编辑了 `~/.bub/config.yml`。参见 [配置](/zh-cn/docs/app/configure/)。 -- 对于每个非 CLI 渠道,相应凭据已设置(如 `BUB_TELEGRAM_TOKEN`)。 - -## 1. 选择启用哪些 channel - -`bub gateway` 监听所有已启用、配置可用的渠道。它默认启用 `all`,展开为所有已启用且不是 `Interface` 的渠道。这既包括 Telegram 这类监听渠道,也包括 `mcp.lifecycle` 这类 `Lifecycle` 后台服务。内置 `cli` 交互接口会被排除,避免长期运行的网关抢占运维者的终端。 - -启用单个渠道: - -```bash -bub gateway --enable-channel telegram -``` - -启用多个: - -```bash -bub gateway --enable-channel telegram --enable-channel wechat -``` - -排除默认生命周期渠道: - -```bash -bub gateway --enable-channel all --enable-channel '!mcp.lifecycle' -``` - -要在配置中固化默认值,设置 `enabled_channels`(逗号分隔,或 `all`): - -```yaml -enabled_channels: telegram -``` - -或通过环境变量: - -```bash -BUB_ENABLED_CHANNELS=telegram -``` - -只有当渠道的 `enabled` 属性返回 `True` 时才会启动。例如 `BUB_TELEGRAM_TOKEN` 为空时 Telegram 渠道返回 `False`,此时被静默跳过而不报错。 - -## 2. 调优防抖与批处理 - -非 CLI 渠道可以通过防抖,把用户连续发送的短消息合并为一轮处理。可调整以下参数: - -| 变量 | 默认值 | 含义 | -|---|---|---| -| `BUB_DEBOUNCE_SECONDS` | `1.0` | 一条活跃消息后必须保持安静的时间,超过则刷新批次。 | -| `BUB_MAX_WAIT_SECONDS` | `10.0` | 后续消息能延长等待的硬上限。 | -| `BUB_ACTIVE_TIME_WINDOW` | `60.0` | 非活跃消息仍会被视为活跃对话一部分的时间窗口。 | - -Bub 按会话暂存这些消息,到时合并为一个 `ChannelMessage`。 - -:::tip -逗号命令(以 `,` 开头的输入)完全绕过防抖。它们一到达就被分发,适合 `,quit`、`,help`,以及任意工具名。 -::: - -## 3. 理解 session id - -Bub 用 `session_id` 索引会话状态。默认规则是 `:`: - -- `bub run "..."`(无 `--session-id`)→ `cli:local` -- Telegram 入站 → `telegram:` -- `bub chat` REPL → `cli_session`(字面字符串,不遵循 `:` 模式) - -需要让多个接入面共享状态时,用 `bub run` 或 `bub chat` 上的 `--session-id` 覆盖。 - -## 4. 内置 channel - -| Channel | 默认启用? | 防抖? | 页面 | -|---|---|---|---| -| `cli` | 仅在 `bub chat` 中 | 否 | [CLI](/zh-cn/docs/app/chat/) | -| `telegram` | 是(当 `BUB_TELEGRAM_TOKEN` 已设置时) | 是 | [Telegram](/zh-cn/docs/app/telegram/) | - -插件可通过 `provide_channels` hook 注册更多渠道。其他渠道可以通过[插件](/zh-cn/docs/app/plugins/)接入。 - -## 后续阅读 - -- [CLI](/zh-cn/docs/app/chat/):REPL 与单次轮次。 -- [Telegram](/zh-cn/docs/app/telegram/):token、白名单、群聊行为。 -- [设置参考](/zh-cn/docs/reference/settings/):全部渠道变量。 diff --git a/website/src/content/docs/zh-cn/docs/app/hooks.mdx b/website/src/content/docs/zh-cn/docs/app/hooks.mdx index a2fedb517..401466e1a 100644 --- a/website/src/content/docs/zh-cn/docs/app/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/app/hooks.mdx @@ -1,11 +1,11 @@ --- -title: 改变应用行为 +title: Hooks description: 通过 hooks 调整提示词、控制调用、保存应用状态并提供资源。 sidebar: - order: 2 + order: 8 --- -在你想改变的位置选择 hook,其余部分继续使用 Bub 应用的实现。提示词 hook 改变输入,工具 decision 控制一次调用,存储 provider 决定会话历史保存在哪里。 +在你想改变的位置选一个 hook(钩子),其余部分继续用 Bub 应用的实现。提示词 hook 改变输入,工具 decision 控制一次调用,存储 provider 决定会话历史保存在哪里。 把这些例子放进[插件](/zh-cn/docs/app/plugins/)。模块级函数可以使用模块入口点;类需要注册实例,或使用接收 framework 的工厂。 diff --git a/website/src/content/docs/zh-cn/docs/app/index.mdx b/website/src/content/docs/zh-cn/docs/app/index.mdx deleted file mode 100644 index b51bbaa35..000000000 --- a/website/src/content/docs/zh-cn/docs/app/index.mdx +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: 使用 Bub 应用 -description: 运行 Bub 应用、按你的工作需要配置它,并决定要添加哪些能力。 -sidebar: - order: 0 ---- - -从能运行的 Bub 应用开始,添加工作中需要的行为。内置 `BubAgent` 扩展了 `core.Agent`;Framework 负责应用编排、插件 hooks 和渠道路由。 - -下面的指南沿用 Bub 应用的配置和扩展接口。如果你希望在 Python 程序中直接传入模型和工具,请从[构建你的 Bub Agent](/zh-cn/docs/sdk/)开始。 - -## 运行应用 - -完成[应用安装](/zh-cn/docs/getting-started/install/#app-install),[运行第一轮对话](/zh-cn/docs/app/first-session/),再按需要[配置模型和工作区](/zh-cn/docs/app/configure/)。 - -日常使用中,选择 Bub 以什么方式触达你: - -- [使用终端](/zh-cn/docs/app/chat/):执行一个任务,或继续交互式对话。 -- [接入 Telegram](/zh-cn/docs/app/telegram/):设置机器人,选择允许使用它的人。 -- [运行网关](/zh-cn/docs/app/gateway/):在一个进程里监听所有已启用的渠道。 -- [部署应用](/zh-cn/docs/app/deploy/):构建容器,在重启后保留数据。 - -## 选择要添加的能力 - -| 你要完成的工作 | 从这里开始 | -| --- | --- | -| 让模型调用一个函数 | [Bub 应用中的工具](/zh-cn/docs/app/tools/) | -| 给模型提供可复用的操作说明 | [Skills](/zh-cn/docs/app/skills/) | -| 打包集成或替换应用 hook | [插件](/zh-cn/docs/app/plugins/)与 [Hooks](/zh-cn/docs/app/hooks/) | -| 通过 CLI、Telegram 或其他适配器接收消息 | [渠道](/zh-cn/docs/app/gateway/) | -| 打包自己的发行版 | [打包 Bub 应用](/zh-cn/docs/app/package/) | -| 持续运行 Bub 服务 | [部署](/zh-cn/docs/app/deploy/) | - -工具、会话历史和 Agent 事件遵循[共同契约](/zh-cn/docs/concepts/)。应用还提供工具加载、代码模式选择等默认策略。[Bub 应用中的工具](/zh-cn/docs/app/tools/)说明这些策略;[轮次](/zh-cn/docs/concepts/turn/)说明 Framework 如何连接消息接收与响应发送。 - -## 复用已有集成 - -- 在 [Bub Hub](https://hub.bub.build) 浏览插件、skills 与发行版。 -- 按照[集成指南](/zh-cn/docs/integrations/)接入具体服务和协议。 -- 在[应用参考](/zh-cn/docs/reference/#application)中查阅签名与默认值。 - -如果你的服务直接管理 `core.Agent`,请沿[用 FastAPI 提供 Agent 服务](/zh-cn/docs/sdk/web-service/)继续。该路线在 Python 中管理模型和存储。 diff --git a/website/src/content/docs/zh-cn/docs/app/language-extensions.mdx b/website/src/content/docs/zh-cn/docs/app/language-extensions.mdx deleted file mode 100644 index 959bae5cc..000000000 --- a/website/src/content/docs/zh-cn/docs/app/language-extensions.mdx +++ /dev/null @@ -1,150 +0,0 @@ ---- -title: 用其他语言实现 hooks -description: 通过实验性的 Extism 插件,用 WebAssembly 实现部分应用 hooks。 -sidebar: - badge: { text: '实验性', variant: 'caution' } - order: 5 ---- - -通过实验性的 `bub-extism` 插件,用 Go、Rust 或其他语言实现部分 Bub 应用 hooks。Bub 运行 Python 应用,Extism 加载实现这些 hooks 的 WebAssembly 模块。 - -本指南接入两个示例模块。采用这条路径前,先检查插件支持的 hooks 和数据转换方式。 - -## 准备工作 - -开始前,请确保: - -- 已[安装 Bub 应用](/zh-cn/docs/getting-started/install/#app-install),并确认 `bub --help` 可用。 -- 如果要构建 Go 示例,需要安装 Go 工具链。 -- 如果要构建 Rust 示例,需要安装 Rust 工具链,并添加 `wasm32-unknown-unknown` target。 - -插件文档列出了支持的 hooks 和模块格式: - -- [`bub-extism` README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) -- [`bub-extism` 示例](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism/examples) -- [Extism manifest 参考](https://extism.org/docs/concepts/manifest/) -- [Bub hooks 参考](/zh-cn/docs/reference/hooks/) - -## 第一步:安装实验性插件 - -将 `bub-extism` 安装到 Bub 所在的同一环境: - -```bash -bub install bub-extism@main -``` - -安装后,确认 Bub 已经识别到它: - -```bash -bub hooks -``` - -你应该能在输出中看到 `extism` 与 `builtin` 并列出现。 - -## 第二步:用 Go 和 Rust 各实现一个 hook - -使用 `bub-extism` 自带的两个示例模块: - -- `go-build-prompt` 实现了 `build_prompt` hook -- `rust-run-model` 实现了 `run_model` hook - -构建 Go 示例: - -```bash -cd bub-contrib/packages/bub-extism/examples/go-build-prompt -GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o go-build-prompt.wasm . -``` - -构建 Rust 示例: - -```bash -cd bub-contrib/packages/bub-extism/examples/rust-run-model -cargo build --release --target wasm32-unknown-unknown -``` - -如果需要完整的构建命令,请参考 [examples 指南](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism/examples)。 - -## 第三步:用 extism.json 连接两个模块 - -在项目根目录创建 `extism.json`,将提示词 hook 绑定到 Go 模块,model hook 绑定到 Rust 模块: - -```json -{ - "plugins": { - "prompt": { - "manifest": { - "wasm": [ - { - "path": "bub-contrib/packages/bub-extism/examples/go-build-prompt/go-build-prompt.wasm" - } - ] - }, - "wasi": true, - "hooks": { - "build_prompt": "build_prompt" - } - }, - "model": { - "manifest": { - "wasm": [ - { - "path": "bub-contrib/packages/bub-extism/examples/rust-run-model/target/wasm32-unknown-unknown/release/bub_extism_rust_run_model.wasm" - } - ] - }, - "hooks": { - "run_model": "run_model" - } - } - } -} -``` - -这个配置文件的关键点: - -- `manifest` 遵循标准 Extism manifest 格式。 -- `hooks` 把 Bub 的 hook 名称映射到 wasm 模块的导出函数。 -- Bub 仍然负责整体调度和优先级管理。 - -配置完成后,设置环境变量让 Bub 找到它: - -```bash -export BUB_EXTISM_CONFIG_PATH=./extism.json -``` - -## 第四步:跑一次跨语言的对话轮次 - -现在让 Bub 先用 Go 构建提示词,再交给 Rust 执行 model hook: - -```bash -bub run "Say hello from a prompt built outside Python." -``` - -如果一切配置正确,整个流程是: - -1. Bub 调用 Go 模块中的 `build_prompt` -2. 将返回的提示词传递给 Rust 模块中的 `run_model` -3. Rust 模块返回最终结果,完成本次对话轮次 - -CLI 会按 `[channel:chat_id]` 的格式输出消息,默认的 `bub run` 通常会先显示 `[cli:local]`,然后是实际内容。 - -示例输出大致如下: - -```text -[cli:local] -[rust-run-model:cli:local] [go-build-prompt:cli:local] Say hello from a prompt built outside Python. -``` - -Go 模块会在入站提示词前加上 `[go-build-prompt:]` 前缀;Rust 模块再把 Go 的输出包一层 `[rust-run-model:]`。开头的 `[cli:local]` 是 CLI 通道发出的频道头。 - -## 5. 检查接入边界 - -Go 模块构造提示词,Rust 模块按示例格式返回响应。这个示例验证 hook 加载和数据传递。需要内置 Agent 的模型调用和会话记录时,保留它的 `run_model` 实现。 - -在插件文档中核对所需 hook 的支持情况,并验证 Python 与 WebAssembly 之间传递的参数和返回值。 - -## 下一步 - -- [构建插件](/zh-cn/docs/app/plugins/):为 Bub 应用打包 Python 集成。 -- [`bub-extism` README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism):阅读 package 约定及支持的 hooks。 -- [Extism 概览](https://extism.org/docs/overview):了解 Extism 如何在不同语言中运行 wasm 模块。 diff --git a/website/src/content/docs/zh-cn/docs/app/plugins.mdx b/website/src/content/docs/zh-cn/docs/app/plugins.mdx index 14d0798ea..80eebc413 100644 --- a/website/src/content/docs/zh-cn/docs/app/plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/app/plugins.mdx @@ -2,10 +2,10 @@ title: 插件 description: 编写插件、选择入口点形式,并安装到应用里。 sidebar: - order: 0 + order: 7 --- -插件可以改变 Bub 应用处理任务的方式。本页先带你写一个回声插件,无需模型凭据就能检查插件发现和轮次处理,再说明入口点的几种形式、工具注册和分享方式。 +插件可以改变 Bub 应用处理任务的方式。本页先带你写一个回声插件,无需模型凭据就能检查插件发现和轮次处理,再说明入口点的几种形式、工具注册、分享方式,以及用其他语言实现的办法。 Python 包在 `bub` 入口点组中声明目标,Bub 导入这个目标,并注册其中的 hook 实现。你也可以在自己的服务中复用现有插件,继续使用宿主自己的调用入口;参见[借用应用运行时](/zh-cn/docs/sdk/app-plugins/)。 @@ -143,7 +143,7 @@ class MyPlugin: return f"Describe the workspace at {self.framework.workspace}." ``` -这些例子会替换提示词。请在[改变应用行为](/zh-cn/docs/app/hooks/)中选择真正适合任务的 hook。 +这些例子会替换提示词。请在[Hooks](/zh-cn/docs/app/hooks/)中选择真正适合任务的 hook。 ## 导入时注册工具 @@ -153,10 +153,46 @@ class MyPlugin: from . import tools # noqa: F401 ``` -应用创建 `BubAgent` 时会获取已注册工具的快照。[添加应用工具](/zh-cn/docs/app/tools/)会说明注册、延迟加载和代码模式。 +应用创建 `BubAgent` 时会获取已注册工具的快照。[工具](/zh-cn/docs/app/tools/)会说明注册、延迟加载和代码模式。 ## 分享插件 -发布前,构建 wheel 并安装到独立环境,确认入口点能加载,并用一个实际任务检查 hook。如果要同时发布 skills 或多个插件,参见[打包 Bub 应用](/zh-cn/docs/app/package/)。 +发布前,构建 wheel 并安装到独立环境,确认入口点能加载,并用一个实际任务检查 hook。如果要同时发布技能或多个插件,参见[分发](/zh-cn/docs/app/distribution/)。 -可移植的 Agent Plugins 使用另一种格式,包括 `plugin.json`、skills 和 MCP servers。[Agent Plugins 集成](/zh-cn/docs/integrations/agent-plugins/)通过一个 Python 插件加载这些包。 +可移植的 Agent Plugins 使用另一种格式,包括 `plugin.json`、技能和 MCP servers。[Agent Plugins 集成](/zh-cn/docs/integrations/agent-plugins/)通过一个 Python 插件加载这些包。 + + + +## WASM 插件(实验性) + +插件不一定要用 Python 写。[`bub-extism`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) 插件运行 WebAssembly 模块,把模块导出的函数当作 hook。hook 的分发和优先级仍由 Bub 决定,Extism 只负责调用你的模块。 + +把它装到 Bub 所在的环境: + +```bash +bub install bub-extism@main +bub hooks +``` + +`extism.json` 为每个模块指定文件和它实现的 hook。`manifest` 用 Extism 的格式,`hooks` 把 Bub 的 hook 名映射到 wasm 导出: + +```json +{ + "plugins": { + "prompt": { + "manifest": { "wasm": [{ "path": "go-build-prompt.wasm" }] }, + "wasi": true, + "hooks": { "build_prompt": "build_prompt" } + } + } +} +``` + +```bash +export BUB_EXTISM_CONFIG_PATH=./extism.json +bub run "Say hello from a prompt built outside Python." +``` + +[README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism)写明了这条路径支持哪些 hook,以及参数怎么转换。用之前先核对这两点:不是每个 hook 都能用,而且所有值都要跨过 Python 和 wasm 的边界。[示例](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism/examples)分别用 Go 和 Rust 各写了一个模块。 + +模型调用和 tape 记录继续用内置实现。这种模块适合只做少量独立工作的 hook。 diff --git a/website/src/content/docs/zh-cn/docs/app/skills.mdx b/website/src/content/docs/zh-cn/docs/app/skills.mdx index b766cfede..92aeeb256 100644 --- a/website/src/content/docs/zh-cn/docs/app/skills.mdx +++ b/website/src/content/docs/zh-cn/docs/app/skills.mdx @@ -1,17 +1,17 @@ --- -title: Skills +title: 技能 description: 编写可复用的流程,确认应用能加载它,并打包给其他用户使用。 sidebar: - order: 0 + order: 6 --- -skill 为 Bub 应用提供完成任务的流程。本页先带你写一个项目 skill,再说明文件字段、发现顺序和打包方式。项目专用的 skills 可以留在工作区,也可以随 Python 包分发,让其他用户安装使用。 +技能是一份写好的流程,让 Bub 应用按步骤完成任务。本页先带你写一个项目技能,再说明文件字段、发现顺序和打包方式。项目专用的技能可以留在工作区,也可以随 Python 包分发,让别人装上就能用。 -自动发现 skills 是应用提供的行为;普通 `core.Agent` 使用你显式传入的指令。 +自动发现技能是应用提供的行为。普通的 `core.Agent` 只用你显式传进去的指令。 ## 编写指令 -创建 skill 目录: +创建技能目录: ```bash mkdir -p .agents/skills/repo-map @@ -30,7 +30,7 @@ description: 修改代码前,先阅读本地仓库结构。 3. 编辑代码前,说明会涉及哪些模块。 ``` -`name` 必须与目录名一致,使用连字符分隔的小写单词。description 帮助模型判断何时使用这个 skill。 +`name` 必须与目录名一致,使用连字符分隔的小写单词。description 帮助模型判断何时使用这个技能。 ## 检查能否加载 @@ -38,32 +38,32 @@ description: 修改代码前,先阅读本地仓库结构。 bub run ",skill name=repo-map" ``` -命令会打印 skill 的指令,不调用模型。如果返回 `(no such skill)`,检查文件路径和 YAML frontmatter。 +命令会打印技能的指令,不调用模型。如果返回 `(no such skill)`,检查文件路径和 YAML frontmatter。 ## 在对话中使用 配置好模型后,运行: ```bash -bub run '使用 $repo-map skill,然后列出你找到的顶层模块。' +bub run '使用 $repo-map 技能,然后列出你找到的顶层模块。' ``` -`$repo-map` 会把该 skill 的指令加入本轮系统提示词。模型随后使用应用中可用的工具执行这些步骤。这个例子需要在执行环境中安装 `rg`。 +`$repo-map` 会把该技能的指令加入本轮系统提示词。模型随后使用应用中可用的工具执行这些步骤。这个例子需要在执行环境中安装 `rg`。 -## skill 文件字段 +## 技能文件字段 -每个 skill 目录包含带 YAML frontmatter 的 `SKILL.md`: +每个技能目录包含带 YAML frontmatter 的 `SKILL.md`: ```markdown --- name: my-skill -description: 说明这个 skill 帮助 agent 完成什么任务。 +description: 说明这个技能帮助 agent 完成什么任务。 --- 在这里编写流程。 ``` -Bub 发现 skills 时会检查这些字段: +Bub 发现技能时会检查这些字段: | 字段 | 要求 | | --- | --- | @@ -71,23 +71,23 @@ Bub 发现 skills 时会检查这些字段: | `description` | 非空字符串,最多 1,024 个字符 | | `metadata` | 可选映射,键和值都必须是字符串 | -流程需要配套文件时,可以在 `SKILL.md` 旁添加 `scripts/`、`references/` 或 `assets/`。正文中的 `$SKILL_DIR` 会展开为 skill 所在目录,`$PYTHON` 会展开为运行 Bub 的解释器路径。应用配置可以用 `${config.path.to.value}` 引用。 +流程需要配套文件时,可以在 `SKILL.md` 旁添加 `scripts/`、`references/` 或 `assets/`。正文中的 `$SKILL_DIR` 会展开为技能所在目录,`$PYTHON` 会展开为运行 Bub 的解释器路径。应用配置可以用 `${config.path.to.value}` 引用。 -## 确认应用会选择哪个 skill +## 确认应用会选择哪个技能 -应用默认依次搜索以下位置,同名时使用最先找到的 skill: +应用默认依次搜索以下位置,同名时使用最先找到的技能: 1. `/.agents/skills/` 2. `~/.agents/skills/` -3. 已安装的 `skills` 命名空间包,包括 Bub 自带的 skills +3. 已安装的 `skills` 命名空间包,包括 Bub 自带的技能 -因此,项目中的 skill 可以覆盖包内同名的 skill。`BubAgent(skill_dirs=...)` 会用指定目录替代默认搜索位置。 +因此,项目中的技能可以覆盖包内同名的技能。`BubAgent(skill_dirs=...)` 会用指定目录替代默认搜索位置。 -系统提示词会列出可用 skills。`$skill-name` 在当前轮展开该 skill;`,skill name=skill-name` 则通过命令路径打印正文。 +系统提示词会列出可用技能。`$skill-name` 在当前轮展开该技能;`,skill name=skill-name` 则通过命令路径打印正文。 -## 把 skills 放进 wheel +## 把技能放进 wheel 在 Python 代码旁放置 `skills` 命名空间包: @@ -111,18 +111,18 @@ src/ packages = ["src/bub_myplugin", "src/skills"] ``` -`skills/` 保持为不含 `__init__.py` 的命名空间包,让多个已安装的包都能提供 skills。其他构建工具也应生成相同的安装目录结构。 +`skills/` 保持为不含 `__init__.py` 的命名空间包,让多个已安装的包都能提供技能。其他构建工具也应生成相同的安装目录结构。 -Skills 可以随插件或应用发行包分发。Bub 通过安装后的 `skills` 命名空间包发现它们。 +技能可以随插件或应用发行包分发。Bub 通过安装后的 `skills` 命名空间包发现它们。 ## 检查安装结果 -把构建好的 wheel 安装到运行 Bub 的环境,再让应用加载 skill: +把构建好的 wheel 安装到运行 Bub 的环境,再让应用加载技能: ```bash bub run ",skill name=my-skill" ``` -如果返回 `(no such skill)`,检查目录名、frontmatter 和 wheel 内容。如果打印了意外的版本,检查项目或用户目录下是否有同名 skill。 +如果返回 `(no such skill)`,检查目录名、frontmatter 和 wheel 内容。如果打印了意外的版本,检查项目或用户目录下是否有同名技能。 -要一起分发 CLI 和插件,参见[打包 Bub 应用](/zh-cn/docs/app/package/)。 +要一起分发 CLI 和插件,参见[分发](/zh-cn/docs/app/distribution/)。 diff --git a/website/src/content/docs/zh-cn/docs/app/telegram.mdx b/website/src/content/docs/zh-cn/docs/app/telegram.mdx deleted file mode 100644 index 8d7ebfaf9..000000000 --- a/website/src/content/docs/zh-cn/docs/app/telegram.mdx +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: 接入 Telegram -description: 配置内置 Telegram 机器人,token、白名单、会话 ID、防抖行为,以及如何启动网关。 ---- - -把 Bub 应用接入 Telegram 机器人,选择允许使用它的人,再启动网关。 - -Telegram 渠道注册名为 `telegram`。它通过 `python-telegram-bot` 使用长轮询,当 `BUB_TELEGRAM_TOKEN` 已设置时在运行时被启用。 - -## 开始之前 - -- 已通过 [@BotFather](https://t.me/BotFather) 创建机器人并拿到 token。 -- 群聊使用时,**已在 BotFather 中关闭 Privacy Mode**。否则机器人只能看到命令,入站过滤器无法判断是否要回复。 -- Bub 已配置可用的模型:参见 [配置](/zh-cn/docs/app/configure/)。 - -## 1. 设置 token - -```bash -BUB_TELEGRAM_TOKEN=123456:abcdef... -``` - -这一个变量就是开关。未设置时渠道报告 `enabled=False`,`bub gateway` 会跳过它且不报错。 - -## 2. 限制谁可以使用机器人 - -白名单使用逗号分隔,对用户名区分大小写: - -```bash -BUB_TELEGRAM_ALLOW_USERS=123456789,your_username -BUB_TELEGRAM_ALLOW_CHATS=-1001234567890,-1009876543210 -``` - -行为: - -- 设置 `BUB_TELEGRAM_ALLOW_CHATS` 后,其他聊天的消息被静默丢弃。 -- 设置 `BUB_TELEGRAM_ALLOW_USERS` 后,其他用户会收到 `Access denied.` -- 用户 **数字 id 或用户名** 任一命中即视为通过。 - -:::caution -两份白名单都为空意味着任何找到机器人的人都能驱动它。生产部署至少设置一份。 -::: - -## 3. 可选:走代理 - -```bash -BUB_TELEGRAM_PROXY=http://127.0.0.1:7890 -# 或 socks5://user:pass@host:port -``` - -同一代理同时用于轮询连接与出站 API 调用。 - -## 4. 启动 gateway - -```bash -bub gateway --enable-channel telegram -``` - -`--enable-channel telegram` 把监听器固定到一个渠道。不带它时,`bub gateway` 会启用所有不是 `Interface` 的默认运行时渠道,其中会在 token 已设置时包含 Telegram,并同时启动已启用的 `Lifecycle` 后台服务。启动时应看到: - -```text -telegram.start allow_users_count=2 allow_chats_count=1 proxy_enabled=False -telegram.start polling -``` - -## 5. 入站消息如何被构造 - -Telegram 渠道使用 `telegram:` 作为会话 ID。即便同一用户既在私聊又在群聊里,二者也保持独立。 - -入站处理: - -- `/start` 由内部处理;机器人回复固定欢迎语(不在白名单的聊天则回复"不允许"提示)。 -- `/bub ` 会被规范化:去掉 `/bub ` 前缀,剩余作为提示词内容。 -- 私聊中,所有非命令消息都会被采集。 -- 群聊中,每条非命令消息都会到达 Telegram 渠道。提到 `@`、包含关键字 `bub`、或回复机器人自己消息的内容会被标记为活跃。其他群消息会被忽略,除非同一会话在 `BUB_ACTIVE_TIME_WINDOW` 内已有活跃对话,此时它们会并入该批次。 - -## 6. 防抖 - -Telegram 渠道设置 `needs_debounce=True`。入站消息按会话用 `BUB_DEBOUNCE_SECONDS`、`BUB_MAX_WAIT_SECONDS` 与 `BUB_ACTIVE_TIME_WINDOW` 进行批处理。具体值见 [渠道总览](/zh-cn/docs/app/gateway/)。 - -:::tip -逗号命令(`,help`、`,fs.read ...`)始终绕过防抖。无论是否有待处理批次,它们一到达就被分发。 -::: - -## 7. 出站行为 - -普通 Telegram 入站消息进入应用轮次流程前会设置 `output_channel="null"`。这会阻止普通模型返回值被盲目发回 Telegram。内置 `telegram` skill 是回复、进度更新和编辑消息的标准路径。 - -逗号命令不同:它们保留 `output_channel="telegram"`,因此工具输出会发回原 `chat_id`。当路由到 `TelegramChannel.send(...)` 的内容是带 `"message"` 字段的 JSON 时,只发送该字段;否则发送原始内容。空载荷被丢弃。 - -## 后续阅读 - -- [渠道总览](/zh-cn/docs/app/gateway/):防抖、`enabled_channels`、会话 ID。 -- [部署](/zh-cn/docs/app/deploy/):把机器人打入容器并持久化状态。 -- [设置参考](/zh-cn/docs/reference/settings/):全部 `BUB_TELEGRAM_*` 变量。 diff --git a/website/src/content/docs/zh-cn/docs/app/chat.mdx b/website/src/content/docs/zh-cn/docs/app/terminal.mdx similarity index 93% rename from website/src/content/docs/zh-cn/docs/app/chat.mdx rename to website/src/content/docs/zh-cn/docs/app/terminal.mdx index 8f487b2d2..2ea532402 100644 --- a/website/src/content/docs/zh-cn/docs/app/chat.mdx +++ b/website/src/content/docs/zh-cn/docs/app/terminal.mdx @@ -1,6 +1,8 @@ --- -title: 使用终端 +title: 终端 description: 用 bub chat 继续对话,或用 bub run 从 shell 执行一个任务。 +sidebar: + order: 2 --- Bub 应用提供两种终端用法:`bub chat` 用于交互式对话,`bub run` 用于执行一条消息。先[配置模型](/zh-cn/docs/app/configure/)。 @@ -41,4 +43,4 @@ bub run ",fs.read path=README.md" 在交互式对话中也可以输入这些命令。Ctrl-X 切换 shell 模式,为输入自动添加命令前缀。 -CLI 使用前台终端,因此网关默认不启动它。网关的渠道选择和消息合并规则见[渠道](/zh-cn/docs/app/gateway/),全部参数见 [CLI 参考](/zh-cn/docs/reference/cli/)。 +CLI 使用前台终端,因此网关默认不启动它。网关的渠道选择和消息合并规则见[渠道](/zh-cn/docs/app/channels/),全部参数见 [CLI 参考](/zh-cn/docs/reference/cli/)。 diff --git a/website/src/content/docs/zh-cn/docs/app/tools.mdx b/website/src/content/docs/zh-cn/docs/app/tools.mdx index df78a0ebe..ac15c8a17 100644 --- a/website/src/content/docs/zh-cn/docs/app/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/app/tools.mdx @@ -1,11 +1,11 @@ --- -title: 添加应用工具 +title: 工具 description: 为 BubAgent 注册工具,并选择应用如何向模型提供它们。 sidebar: - order: 3 + order: 5 --- -给 Bub 应用添加工作中需要执行的动作。[公共工具指南](/zh-cn/docs/sdk/tools/)说明函数参数、结果和工具上下文。本页介绍应用注册,以及 `BubAgent` 执行的工具策略。 +给 Bub 应用添加工作中需要执行的动作。[工具与 CodeMode](/zh-cn/docs/sdk/tools/)说明函数参数、结果和工具上下文。本页介绍应用注册,以及 `BubAgent` 执行的工具策略。 ## 注册工具 @@ -99,12 +99,12 @@ def get_order(order_id: str) -> dict[str, str]: 内置 `bash`、`bash.output`、`bash.kill`、`fs.*` 和 `run_code` 使用会话的 `Environment`。默认是运行在宿主上的 `LocalEnvironment`。 -插件通过 `provide_environment(session_id, workspace)` hook 提供其他环境。Framework 为每个会话缓存一个环境,并在退出 `framework.running()` 时关闭。[公共工具指南](/zh-cn/docs/sdk/tools/#run-tools-in-an-environment)说明环境接口。 +插件通过 `provide_environment(session_id, workspace)` hook 提供其他环境。Framework 为每个会话缓存一个环境,并在退出 `framework.running()` 时关闭。[工具与 CodeMode](/zh-cn/docs/sdk/tools/#run-tools-in-an-environment)说明环境接口。 自定义应用工具可以用 `bub.builtin.environment` 中的 `environment_from_state(context.state)` 取得环境。Python 工具本身在宿主侧运行,需要时再显式调用环境来完成相应操作。 ## 配合工具添加指令 -把工作区通用指令写入 `AGENTS.md`,用 [skills](/zh-cn/docs/app/skills/)提供按任务加载的流程。Skill 可以告诉模型何时调用工具,Python 函数则定义工具执行的动作。 +把工作区通用指令写入 `AGENTS.md`,用 [技能](/zh-cn/docs/app/skills/)提供按任务加载的流程。技能可以告诉模型何时调用工具,Python 函数则定义工具执行的动作。 逗号命令让使用者无需请求模型,就能调用已注册的工具。语法见[文本命令](/zh-cn/docs/sdk/tools/#commands)。 diff --git a/website/src/content/docs/zh-cn/docs/app/workspace.mdx b/website/src/content/docs/zh-cn/docs/app/workspace.mdx new file mode 100644 index 000000000..8f455737b --- /dev/null +++ b/website/src/content/docs/zh-cn/docs/app/workspace.mdx @@ -0,0 +1,48 @@ +--- +title: 工作区 +description: 建一个工作区,放进去你的指令,再确认命令能用。 +sidebar: + order: 0 +--- + +Bub 在一个目录里工作,这个目录就是工作区。它在这里读写文件,也从这里取项目的指令。 + +这一页建一个工作区,放进去你的指令,再确认应用的命令能跑。检查命令不需要模型凭据。 + +## 准备工作区 + +给项目建一个目录,后面的命令都在里面跑: + +```bash +mkdir hello-bub +cd hello-bub +``` + +把工作指令写进 `AGENTS.md`: + +```bash +cat > AGENTS.md <<'EOF' +你是这个工作区的 Bub agent。 +编辑文件前先阅读相关内容。 +回复保持简短、具体。 +EOF +``` + +应用构建系统提示词时会读这个文件。用一段时间后,你自然会知道哪些指令有用,再改它。 + +## 检查命令 + +```bash +bub run ",help" +``` + +输出会列出 `,skill`、`,tape.info` 这类命令。这一步只检查应用的命令路径,不调用模型。 + +命令以行首的逗号开头。未知命令会当成 shell 命令执行,所以检查时用上面这种已知命令。 + +## 下一步 + +- 增加[项目技能](/zh-cn/docs/app/skills/),让 Bub 按你的流程走。 +- 增加[工具](/zh-cn/docs/app/tools/),让 Bub 调你的代码。 +- 写[插件](/zh-cn/docs/app/plugins/),改应用的行为。 +- 在[终端](/zh-cn/docs/app/terminal/)里聊一次,或者[接一个渠道](/zh-cn/docs/app/channels/)。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/events.mdx b/website/src/content/docs/zh-cn/docs/concepts/events.mdx index 2d887616f..5f4531439 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/events.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/events.mdx @@ -132,6 +132,6 @@ async def mark_reviewed() -> None: 这个适配器遵循插件 hook 的顺序。单个拦截 hook 抛异常时,它接住、记日志、跳过。要拒绝一次调用,就返回对应的 decision。其他应用 hooks 按各自的出错规则处理。 -插件示例见[改变应用行为](/zh-cn/docs/app/hooks/)。顺序和签名见[应用 Hooks](/zh-cn/docs/reference/hooks/)。EventBus 在基础 SDK 安装里就有。 +插件示例见[Hooks](/zh-cn/docs/app/hooks/)。顺序和签名见[应用 Hooks](/zh-cn/docs/reference/hooks/)。EventBus 在基础 SDK 安装里就有。 `framework.events` 发布命令、渠道工作和用户输入,用的是 `bub.application` 里的 `CommandChanged`、`WorkChanged` 和 `PromptChanged`。订阅和取消订阅,还是 `on()` 和 `off()`。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx index 9123cca2d..ca0f78238 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx @@ -1,6 +1,6 @@ --- title: 扩展点 -description: 判断你的任务需要工具、事件处理器、skill、插件,还是渠道。 +description: 判断你的任务需要工具、事件处理器、技能、插件,还是渠道。 sidebar: order: 5 --- @@ -12,15 +12,15 @@ sidebar: | 让 agent 读或改某个东西 | [工具](/zh-cn/docs/sdk/tools/)。 | 内核的通用能力。传给 Agent,或者在应用里注册。 | | 看住或拦下模型、工具调用 | [事件处理器](/zh-cn/docs/concepts/events/)。 | 内核的 EventBus。应用插件可以用对应的拦截 hooks。 | | 改哪段历史变成模型消息 | [TapeContext](/zh-cn/docs/concepts/tape/#context)。 | 传给 Agent,或者通过应用 hook 提供。 | -| 教 agent 一套可复用的做法 | [skill](/zh-cn/docs/app/skills/)。 | Bub 应用负责发现和加载。 | +| 教 agent 一套可复用的做法 | [技能](/zh-cn/docs/app/skills/)。 | Bub 应用负责发现和加载。 | | 改应用怎么初始化,或者消息怎么处理 | [插件](/zh-cn/docs/app/plugins/)。 | `BubFramework` 和它的 hooks。 | -| 接一个消息服务 | [渠道](/zh-cn/docs/app/gateway/)。 | 应用的入站和出站路由。 | +| 接一个消息服务 | [渠道](/zh-cn/docs/app/channels/)。 | 应用的入站和出站路由。 | -## 工具与 skill +## 工具与技能 -工具跑 Python,返回结果。skill 是一个目录,里面有 `SKILL.md`,还可以有别的文件。它的指令可以告诉 agent 怎么用工具。指令里提到的 Python 函数,你自己写,再另外挂上去。 +工具跑 Python,返回结果。技能是一个目录,里面有 `SKILL.md`,还可以有别的文件。它的指令可以告诉 agent 怎么用工具。指令里提到的 Python 函数,你自己写,再另外挂上去。 -普通的 Agent 只用你给的工具和提示词片段。Bub 应用会去发现 skills,也会读 `AGENTS.md`。见 [Skills](/zh-cn/docs/app/skills/)和[应用工具](/zh-cn/docs/app/tools/)。 +普通的 Agent 只用你给的工具和提示词片段。Bub 应用会去发现技能,也会读 `AGENTS.md`。见 [技能](/zh-cn/docs/app/skills/)和[工具](/zh-cn/docs/app/tools/)。 agent 也可以用一条[文本命令](/zh-cn/docs/sdk/tools/#commands)调工具,或者用 CodeMode 让模型写 Python 来调。这几条路走的是同一个工具实现。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/index.mdx b/website/src/content/docs/zh-cn/docs/concepts/index.mdx index 5f97ab3dd..48a8c1c80 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/index.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/index.mdx @@ -13,7 +13,7 @@ Bub 有四个概念。**轮次**把一条消息送过整个应用。**tape** 存 `core.Agent` 接收模型、工具、提示词片段和一个 tape 存储。这些都由你传进来。一个小的 Python 服务,只装内核的依赖就能跑。 -`BubAgent` 加上应用的默认行为:配置、skills、命令、工具选择和 steering。`BubFramework` 在外围管理插件、消息处理和出站路由。需要这些行为时,再引入它们。 +`BubAgent` 加上应用的默认行为:配置、技能、命令、工具选择和 steering。`BubFramework` 在外围管理插件、消息处理和出站路由。需要这些行为时,再引入它们。 `pip install bub` 只装内核。`pip install "bub[app]"` 两层都装。[安装指南](/zh-cn/docs/getting-started/install/)说明依赖怎么选。 @@ -24,7 +24,7 @@ Bub 有四个概念。**轮次**把一条消息送过整个应用。**tape** 存 | 轮次 | 一条进来的消息,以及它引起的全部工作。 | [轮次](/zh-cn/docs/concepts/turn/) | | Tape | 存下来的对话,以及模型实际读到的那一段。 | [Tape](/zh-cn/docs/concepts/tape/) | | 事件 | 报告每一步的总线,处理器可以借它改掉下一步。 | [事件](/zh-cn/docs/concepts/events/) | -| 扩展点 | 新行为放的地方:工具、skill、插件或渠道。 | [扩展点](/zh-cn/docs/concepts/extension-points/) | +| 扩展点 | 新行为放的地方:工具、技能、插件或渠道。 | [扩展点](/zh-cn/docs/concepts/extension-points/) | ## 术语 @@ -32,7 +32,7 @@ Bub 有四个概念。**轮次**把一条消息送过整个应用。**tape** 存 **tape** 存对话。它的**条目**是消息、工具调用、结果和事件。**anchor** 标出上下文的起点。**handoff** 会写一个新的 anchor。本轮的 **state** 是一个独立字典,工具和处理器共用它。 -应用另外加三样东西。**插件**提供 Python hooks。**skills** 写指令。**渠道**收发消息。 +应用另外加三样东西。**插件**提供 Python hooks。**技能** 写指令。**渠道**收发消息。 ## 设计说明 @@ -40,7 +40,7 @@ Bub 有四个概念。**轮次**把一条消息送过整个应用。**tape** 存 历史与上下文分开。tape 留下每一条。上下文挑出下一次模型调用要读的条目。anchor 开一个新的上下文,前面的条目照旧保留。因此你可以查完整历史,同时只发一个短请求。 -每个部分只干一件事。工具做动作。skill 写做法。渠道把应用接到人干活的地方。职责分开,你才知道该改哪一块。 +每个部分只干一件事。工具做动作。技能写做法。渠道把应用接到人干活的地方。职责分开,你才知道该改哪一块。 ## 背景 diff --git a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx index c2b326e01..35668d759 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx @@ -27,7 +27,7 @@ sidebar: ## Agent 调用 -内置的 `run_model_stream` hook 调用 `BubAgent.run_stream()`。`BubAgent` 用的是内核的模型与工具循环,再加上配置、skills、命令、steering 和应用的工具策略。 +内置的 `run_model_stream` hook 调用 `BubAgent.run_stream()`。`BubAgent` 用的是内核的模型与工具循环,再加上配置、技能、命令、steering 和应用的工具策略。 框架在流式与非流式 hook 结果之间做转换。`process_inbound(..., stream_output=True)` 让渠道路由器边收边处理,流结束时返回一个完整的 `TurnResult`。 @@ -45,4 +45,4 @@ sidebar: ## 下一步 -用[插件设置](/zh-cn/docs/app/plugins/)注册你的实现。常见改法见[改变应用行为](/zh-cn/docs/app/hooks/)。精确的签名和顺序见[应用 Hooks](/zh-cn/docs/reference/hooks/)。 +用[插件设置](/zh-cn/docs/app/plugins/)注册你的实现。常见改法见[Hooks](/zh-cn/docs/app/hooks/)。精确的签名和顺序见[应用 Hooks](/zh-cn/docs/reference/hooks/)。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx index f8dad900d..1f76f4a72 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx @@ -19,7 +19,7 @@ Bub 分两层。 **内核**(`bub.core`)有循环、工具接口、对话记录、事件总线和模型客户端。它用 `republic` 调用模型,依赖 `pydantic` 和 `loguru`。 -**应用层**(`bub[app]`)加上让内核变成一个能直接运行的程序所需的部分:配置和配置文件、插件与 hooks、skills、消息渠道、网关,以及 `bub` 命令。 +**应用层**(`bub[app]`)加上让内核变成一个能直接运行的程序所需的部分:配置和配置文件、插件与 hooks、技能、消息渠道、网关,以及 `bub` 命令。 `pip install bub` 只装内核。`pip install "bub[app]"` 两层都装。 @@ -30,7 +30,7 @@ Bub 分两层。 | 运行的是什么 | `bub` 命令,独占一个进程 | 你自己的 Python 程序 | | 怎么启动 | `bub chat`、`bub run`、`bub gateway` | `Agent(...)` | | 你需要提供 | 模型和渠道配置 | 模型、工具、一个 tape | -| Bub 提供 | 配置、插件、skills、hooks、渠道 | 循环和它的事件 | +| Bub 提供 | 配置、插件、技能、hooks、渠道 | 循环和它的事件 | 想要一个在终端或聊天软件里就能用的 agent,用应用。想给自己跑的服务加上 agent 行为,就自己构建。 @@ -62,7 +62,7 @@ Bub 分两层。 其余文档,按阅读顺序: - **[核心概念](/zh-cn/docs/concepts/)**:tape、轮次、事件、扩展点。指南里遇到看不懂的词再回来读,后面的页面不依赖它。 -- **[使用 Bub 应用](/zh-cn/docs/app/)**:先配置,再加工具、skills、插件、hooks 和渠道。 +- **[使用 Bub 应用](/zh-cn/docs/app/workspace/)**:先配置,再加工具、技能、插件、hooks 和渠道。 - **[构建你的 Bub Agent](/zh-cn/docs/sdk/)**:留下内核,循环周边自己写。 - **[集成](/zh-cn/docs/integrations/)**:MCP、ACP、agent plugins、本地模型、SQLite、可观测性。 - **[参考](/zh-cn/docs/reference/)**:CLI 命令、配置项、类型、hooks、版本记录。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx index 7f38916a9..5197ac808 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx @@ -50,7 +50,7 @@ uv tool update-shell bub --help ``` -接着阅读[首次运行 Bub](/zh-cn/docs/app/first-session/),在自己的工作区开始对话。 +接着看[工作区](/zh-cn/docs/app/workspace/),建一个工作区,写下项目指令。 基础安装与 `bub[app]` 包含相同的包文件;extra 改变的是安装的依赖。缺少应用依赖时,`bub` 命令会退出,并提示安装 `bub[app]`。 @@ -95,7 +95,7 @@ requires-python = ">=3.12" dependencies = ["bub>=0.6.0"] ``` -如果包会导入内置 Agent、Framework、插件、skills 或渠道,则声明: +如果包会导入内置 Agent、Framework、插件、技能或渠道,则声明: ```toml dependencies = ["bub[app]>=0.6.0"] diff --git a/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx b/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx index 57f7238ff..6cfaafc5d 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx @@ -35,7 +35,7 @@ bub chat 目录里有 `AGENTS.md` 时 Bub 会读取它,把这一轮记到 tape 上,再用你配置的模型回答。按 `Ctrl+C` 退出。 -下一步:[第一次会话](/zh-cn/docs/app/first-session/)会讲工作区、命令和会话命名。 +下一步:[工作区](/zh-cn/docs/app/workspace/)讲 `AGENTS.md` 和应用的命令。 @@ -86,8 +86,8 @@ uv run python main.py =0.2.0` 和 `bub-agent-plugins`,仅启用 skills 时也需要同时安装。 +请显式安装 `bub-mcp>=0.2.0` 和 `bub-agent-plugins`,仅启用技能时也需要同时安装。 在 hook 报告的 `load_state` 和 `provide_channels` 行中查找 `agent-plugins`。 这表示 Python 接入插件已加载;接下来验证一个可移植插件包。 @@ -75,10 +75,10 @@ Read the supplied notes. List the decisions made, then the remaining actions and If an owner is missing, say that it is unassigned. ``` -skill 的 `name` 必须与所在目录名一致。插件内的 skills 复用 Bub 现有的解析器和发现规则; -工作区与用户目录中的同名 skills 仍然优先。 +技能的 `name` 必须与所在目录名一致。插件内的技能复用 Bub 现有的解析器和发现规则; +工作区与用户目录中的同名技能仍然优先。 -## 3. 验证 skill +## 3. 验证技能 在工作区中,通过 Bub 执行以下 comma 命令: @@ -88,7 +88,7 @@ bub --workspace . run ',skill name=plugin-summary' ``` 第一条命令应列出 `plugin-summary`,第二条应打印它的位置和刚保存的指令。 -加载 skills 不需要后台渠道,因此可以使用 `bub run`。 +加载技能不需要后台渠道,因此可以使用 `bub run`。 ## 4. 添加并调用 MCP 服务器 @@ -175,7 +175,7 @@ agent-plugins: 若多个插件包的清单名称相同,采用第一个有效插件包。 设置 `auto_discover: false` 后只使用显式路径。`skills_enabled` 和 `mcp_enabled` 分别控制两种可移植组件。 -普通 Bub skills 和 home MCP 服务器各自使用自己的配置。 +普通 Bub 技能和 home MCP 服务器各自使用自己的配置。 环境变量以 `BUB_AGENT_PLUGINS_` 为前缀,列表值使用 JSON。例如: @@ -199,12 +199,12 @@ stdio 服务器会收到绝对路径形式的 `PLUGIN_ROOT` 和 `PLUGIN_DATA` | --- | --- | | `bub hooks` 中没有 `agent-plugins` | 确认两个接入插件都安装在 Bub 的活动环境中。缺少 `bub-mcp` 或版本过旧时,加载错误会指出原因。 | | 插件包被拒绝 | 检查 `plugin.json`,包括必填的 `$schema` 和 `name`,并查看 `bub-agent-plugins:` 启动日志。其他插件包仍可加载。 | -| skill 未出现 | 检查 frontmatter 中的名称是否与目录一致,以及 `skills_enabled` 是否开启。工作区或用户目录中的同名 skill 可能优先。 | +| 技能未出现 | 检查 frontmatter 中的名称是否与目录一致,以及 `skills_enabled` 是否开启。工作区或用户目录中的同名技能可能优先。 | | MCP 工具未出现 | 检查插件包的 `mcp.json`、`mcp_enabled`、已启用的渠道和连接日志。等待 bootstrap 完成后再调用工具。 | -| skills 可用但 MCP 不可用 | MCP 校验和连接失败不会影响 skills。无效服务器条目会被跳过,同文件中的其他有效服务器仍可加载。 | +| 技能可用但 MCP 不可用 | MCP 校验和连接失败不会影响技能。无效服务器条目会被跳过,同文件中的其他有效服务器仍可加载。 | ## 下一步 - [连接 MCP 服务器](/zh-cn/docs/integrations/mcp/):管理 Bub 数据目录配置中的服务器。 -- [构建 skills](/zh-cn/docs/app/skills/):编写并验证 Bub skills。 +- [构建技能](/zh-cn/docs/app/skills/):编写并验证 Bub 技能。 - [构建插件](/zh-cn/docs/app/plugins/):使用 Python 包扩展 Bub hooks。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/index.mdx b/website/src/content/docs/zh-cn/docs/integrations/index.mdx index fe91a262e..cd0593efb 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/index.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/index.mdx @@ -16,11 +16,11 @@ sidebar: 这些指南使用应用的 CLI、配置或插件机制。先[安装 Bub 应用](/zh-cn/docs/getting-started/install/),把 Bub 和插件放在同一个环境中。 - [连接 MCP 服务器](/zh-cn/docs/integrations/mcp/):调用 MCP 服务器提供的工具。 -- [加载 Agent Plugins](/zh-cn/docs/integrations/agent-plugins/):加载包含 skills 和 MCP 服务器的可移植插件包。 +- [加载 Agent Plugins](/zh-cn/docs/integrations/agent-plugins/):加载包含技能和 MCP 服务器的可移植插件包。 - [接入 ACP 编辑器](/zh-cn/docs/integrations/acp/):在 Zed 等编辑器中使用 Bub。 - [使用本地 llama.cpp 模型](/zh-cn/docs/integrations/local-models/):让应用调用本地模型服务。 - [把会话历史存入 SQLite](/zh-cn/docs/integrations/storage/):通过插件替换应用的文件存储。 - [检查会话记录和执行轨迹](/zh-cn/docs/integrations/observability/):查看本地记录,或将轨迹导出到 Phoenix 和 Logfire。 -- [用其他语言实现 hooks](/zh-cn/docs/app/language-extensions/):尝试实验性的 WebAssembly 接入。 +- [WASM 插件](/zh-cn/docs/app/plugins/#wasm-plugins):用 Go 或 Rust 写 hook,通过 WebAssembly 运行。 各集成指南都提供插件文档链接,可据此查看该插件的具体配置方式。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx b/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx index 140892dc5..02cb690bb 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx @@ -92,7 +92,7 @@ export BUB_MODEL="openai:gemma-4-E2B-it" bub run "Reply with one short sentence: hello from a local model." ``` -现在 Bub 会通过本地 OpenAI 兼容端点进行模型调用。应用轮次流程、渠道、tools 和 Tape 都不需要改变。 +现在 Bub 会通过本地 OpenAI 兼容端点进行模型调用。应用轮次流程、渠道、工具和 Tape 都不需要改变。 ```bash ~/bubbuild/bub$ bub run "Reply with one short sentence: hello from a local model." diff --git a/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx index 49613d1a9..05ca53221 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx @@ -28,7 +28,7 @@ bub install bub-mcp@main `bub install` 使用当前 Bub 所在的环境。插件来源与依赖处理方式见 [CLI 参考](/zh-cn/docs/reference/cli/#bub-install)。 -[Agent Plugins](/zh-cn/docs/integrations/agent-plugins/) 也可以加载 skills 和自带的 MCP 服务器,与这里的服务器一起使用。该集成使用独立的生命周期渠道,以及包含插件名的工具名称。 +[Agent Plugins](/zh-cn/docs/integrations/agent-plugins/) 也可以加载技能和自带的 MCP 服务器,与这里的服务器一起使用。该集成使用独立的生命周期渠道,以及包含插件名的工具名称。 验证插件已加载: @@ -183,6 +183,6 @@ bub mcp remove time ## 下一步 -- [加载 Agent Plugins](/zh-cn/docs/integrations/agent-plugins/):在 `mcp.json` 之外加载可移植的 skills 和 MCP 服务器。 +- [加载 Agent Plugins](/zh-cn/docs/integrations/agent-plugins/):在 `mcp.json` 之外加载可移植的技能和 MCP 服务器。 - [配置](/zh-cn/docs/app/configure/):Bub 的配置层与环境变量。 - [bub-mcp 源码](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-mcp):插件完整源码与高级选项。 diff --git a/website/src/content/docs/zh-cn/docs/reference/cli.mdx b/website/src/content/docs/zh-cn/docs/reference/cli.mdx index f19b9581c..5d6a1204d 100644 --- a/website/src/content/docs/zh-cn/docs/reference/cli.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/cli.mdx @@ -92,7 +92,7 @@ bub gateway [OPTIONS] - `--uninstall` 会停止并删除对应的 systemd user unit 或 Windows 计划任务。服务已经不存在时仍视为成功,且不要求当前存在已启用的渠道。 - `--install` 与 `--uninstall` 不能同时使用。 -部署细节见 [运维 › Channels](/zh-cn/docs/app/gateway/)。 +部署细节见 [运维 › Channels](/zh-cn/docs/app/channels/)。 ## `bub onboard` diff --git a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx index 30b4a5165..00c1c6db1 100644 --- a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx @@ -3,7 +3,7 @@ title: 应用 Hooks description: Bub 插件的 hook 签名、调用规则和资源生命周期。 --- -Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 个 hooks。用法示例见[改变应用行为](/zh-cn/docs/app/hooks/),应用阶段顺序见[轮次](/zh-cn/docs/concepts/turn/)。 +Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 个 hooks。用法示例见[Hooks](/zh-cn/docs/app/hooks/),应用阶段顺序见[轮次](/zh-cn/docs/concepts/turn/)。 下文的“首个”指按 hook 优先级取得第一个非 `None` 的结果,“全部”指调用每个实现。普通 hook 抛出异常时,本次分发停止,异常向上传播;[agent 拦截 hooks](#agent-loop-hooks-pluginagenthooks) 和 `on_error` 有各自的异常处理规则。 diff --git a/website/src/content/docs/zh-cn/docs/reference/settings.mdx b/website/src/content/docs/zh-cn/docs/reference/settings.mdx index 1a631a389..e15adc136 100644 --- a/website/src/content/docs/zh-cn/docs/reference/settings.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/settings.mdx @@ -3,7 +3,7 @@ title: 应用设置 description: 应用内置环境变量、配置字段和默认值。 --- -这些设置用于配置 Bub 应用。表格列出内置的 `BUB_*` 变量及其 YAML 字段,插件设置由各插件文档说明。操作步骤见[配置应用](/zh-cn/docs/app/configure/)。直接构造的 `core.Agent` 通过 Python 参数接收组件,不加载这些配置。 +这些设置用于配置 Bub 应用。表格列出内置的 `BUB_*` 变量及其 YAML 字段,插件设置由各插件文档说明。操作步骤见[配置](/zh-cn/docs/app/configure/)。直接构造的 `core.Agent` 通过 Python 参数接收组件,不加载这些配置。 ## 优先级 @@ -177,7 +177,7 @@ class TelegramSettings(Settings): | `BUB_TELEGRAM_ALLOW_CHATS` | 未设置 | `allow_chats` | 逗号分隔的允许 Telegram 聊天 ID 列表。留空表示不限制。 | | `BUB_TELEGRAM_PROXY` | 未设置 | `proxy` | 访问 Telegram API 的代理 URL,例如 `http://user:pass@host:port` 或 `socks5://host:port`。 | -部署细节见 [运维 › Channels › Telegram](/zh-cn/docs/app/telegram/)。 +部署细节见 [渠道 › Telegram](/zh-cn/docs/app/channels/#telegram)。 ## Login:第三方环境变量 diff --git a/website/src/content/docs/zh-cn/docs/reference/types.mdx b/website/src/content/docs/zh-cn/docs/reference/types.mdx index d19bfbf51..1f84b501d 100644 --- a/website/src/content/docs/zh-cn/docs/reference/types.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/types.mdx @@ -135,7 +135,7 @@ from bub.builtin.agent import BubAgent 应用配置是进程级共享的。需要使用 framework 当前存储时,在 `framework.running()` 内创建 `BubAgent`。显式提供的 store 优先,生命周期仍由调用方管理。两种 store 都没有时,agent 使用内存存储。 -省略 `state` 时,`run_stream` 加载初始状态;传入 `state` 时,则直接使用调用方提供的状态。状态保存 hooks、出站消息处理和并发轮次调度由宿主管理。完整用法见[嵌入应用](/zh-cn/docs/app/package/#programmatic-embedding)。 +省略 `state` 时,`run_stream` 加载初始状态;传入 `state` 时,则直接使用调用方提供的状态。状态保存 hooks、出站消息处理和并发轮次调度由宿主管理。完整用法见[嵌入应用](/zh-cn/docs/app/distribution/#programmatic-embedding)。 ## 导入路径 diff --git a/website/src/content/docs/zh-cn/docs/reference/versions.mdx b/website/src/content/docs/zh-cn/docs/reference/versions.mdx index fd1ae809b..9d417d2ab 100644 --- a/website/src/content/docs/zh-cn/docs/reference/versions.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/versions.mdx @@ -19,15 +19,15 @@ uv tool install --upgrade --force "bub[app]" uv pip install --upgrade "bub[app]" ``` -插件或发行版的 `pyproject.toml` 也应把普通 `bub` 依赖改为 `bub[app]>=0.6.0`。这个 extra 安装内置运行时、Framework、配置、插件 hooks、skills 和渠道所需的依赖。 +插件或发行版的 `pyproject.toml` 也应把普通 `bub` 依赖改为 `bub[app]>=0.6.0`。这个 extra 安装内置运行时、Framework、配置、插件 hooks、技能和渠道所需的依赖。 -运行 `bub --help`,再[完成一轮对话](/zh-cn/docs/app/first-session/)。检查插件是否加载时,使用 `bub hooks`。 +运行 `bub --help`,再在[终端](/zh-cn/docs/app/terminal/)里跑一轮对话。检查插件是否加载时,使用 `bub hooks`。 ## 按代码用途选择接口 | 代码用途 | 依赖与指南 | | --- | --- | | 直接向 `core.Agent` 传入模型和工具 | `bub`;[构建自己的 Agent](/zh-cn/docs/sdk/) | -| 使用 `BubAgent`、Framework、应用 hooks 或渠道 | `bub[app]`;[定制 Bub 应用](/zh-cn/docs/app/) | +| 使用 `BubAgent`、Framework、应用 hooks 或渠道 | `bub[app]`;[使用 Bub 应用](/zh-cn/docs/app/workspace/) | -直接组合的 Agent 在 Python 中接收模型与组件。Bub 应用通过 `BubAgent` 和 Framework 提供配置、skills 与工具策略。[工具](/zh-cn/docs/sdk/tools/)和 [Agent 事件](/zh-cn/docs/concepts/events/)说明共同契约与默认行为的区别。 +直接组合的 Agent 在 Python 中接收模型与组件。Bub 应用通过 `BubAgent` 和 Framework 提供配置、技能与工具策略。[工具](/zh-cn/docs/sdk/tools/)和 [Agent 事件](/zh-cn/docs/concepts/events/)说明共同契约与默认行为的区别。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/app-plugins.mdx b/website/src/content/docs/zh-cn/docs/sdk/app-plugins.mdx index 48468cfe0..56ac55f27 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/app-plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/app-plugins.mdx @@ -7,7 +7,7 @@ sidebar: 你可以在现有 Python 服务中使用 Bub 应用插件。宿主继续管理 HTTP 路由、任务入口和输出处理;根据要复用的应用行为,选择 Agent 和运行入口。 -`BubAgent` 已经将内核循环、设置、插件 hooks、skills 和工具策略组合在一起,它的[直接流入口](#app-runtime)也可以嵌入宿主程序。需要完整消息处理流程时,使用 `BubFramework.process_inbound()`。 +`BubAgent` 已经将内核循环、设置、插件 hooks、技能和工具策略组合在一起,它的[直接流入口](#app-runtime)也可以嵌入宿主程序。需要完整消息处理流程时,使用 `BubFramework.process_inbound()`。 使用 `core.Agent` 时,由你选择组件并接入 hooks。下面分别取用搜索插件的工具、适配上下文插件的调用 hooks,每个示例都说明了该插件需要的依赖。后台服务还需要各自的启动和关闭流程。 @@ -147,7 +147,7 @@ async with plugins.running(): | --- | --- | | 工具或存储 | 导入公开组件并传给 Agent。例如,`bub_tapestore_sqlalchemy.store.SQLAlchemyTapeStore` 接收数据库 URL,可作为 `store=` 传入。 | | 模型或工具拦截、系统提示词、继续运行或 steering | 注册指定插件并[接入 `AgentPlugins`](#agent-plugins),同时核对插件需要的状态字段和工具。 | -| 消息准备、模型阶段替换、状态保存或消息发送 | 在宿主中[嵌入应用流程](/zh-cn/docs/app/package/#programmatic-embedding),执行这些阶段并取得 `TurnResult`。 | +| 消息准备、模型阶段替换、状态保存或消息发送 | 在宿主中[嵌入应用流程](/zh-cn/docs/app/distribution/#programmatic-embedding),执行这些阶段并取得 `TurnResult`。 | | 后台连接或动态发现的工具 | 启动服务、等待就绪、绑定工具,并随宿主关闭。具体顺序遵循插件自己的生命周期接口。 | 按所选存储的生命周期约定管理资源。`framework.running()` 会获取 hooks 提供的资源,并在退出时关闭它进入的上下文管理器。返回普通对象的存储仍需遵循自己的清理约定。 @@ -158,9 +158,9 @@ async with plugins.running(): ## 复用 Bub 应用的默认行为 -`BubAgent` 在内核循环上接入 Bub 设置、skills、工具策略和插件 hooks。宿主可以通过 `framework.load_hooks()` 加载已安装插件,进入 `framework.running()` 后构造 `BubAgent`。它的 `run_stream()` 返回由宿主消费和关闭的流。 +`BubAgent` 在内核循环上接入 Bub 设置、技能、工具策略和插件 hooks。宿主可以通过 `framework.load_hooks()` 加载已安装插件,进入 `framework.running()` 后构造 `BubAgent`。它的 `run_stream()` 返回由宿主消费和关闭的流。 -如果也需要这些应用默认行为,可以选择这种接法。内置系统提示词不要求 Agent 通过 channel skill 回复;`bub gateway` 和 `bub chat` 背后的 channel manager 会注册一个 `system_prompt` hook,只在它运行的轮次中加入这条指令。自己调用 `process_inbound()` 的服务可以注册自己的 `system_prompt` hook,说明回复如何送达。替换 `agent.system_prompt` 会替换所有片段,包括插件、skills、延迟工具和 CodeMode 的指令。 +如果也需要这些应用默认行为,可以选择这种接法。内置系统提示词不要求 Agent 通过 channel skill 回复;`bub gateway` 和 `bub chat` 背后的 channel manager 会注册一个 `system_prompt` hook,只在它运行的轮次中加入这条指令。自己调用 `process_inbound()` 的服务可以注册自己的 `system_prompt` hook,说明回复如何送达。替换 `agent.system_prompt` 会替换所有片段,包括插件、技能、延迟工具和 CodeMode 的指令。 `run_stream()` 会使用系统提示词、继续提示词、调用拦截 hooks,以及配置好的 Tape 组件。省略 `state` 时,它通过一条只包含 Agent 引用的消息加载状态。插件需要请求元数据时,用 `framework.build_state(message, session_id)` 准备状态,再传给 `run_stream(state=...)`。在 `message["_runtime_agent"]` 中放入 Agent,让状态恢复使用它的存储。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/index.md b/website/src/content/docs/zh-cn/docs/sdk/index.md index 41a164428..6931e7ce4 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/index.md +++ b/website/src/content/docs/zh-cn/docs/sdk/index.md @@ -7,7 +7,7 @@ sidebar: 选择模型,加入工具,把 Agent 接到你的程序里。`bub.core.Agent` 负责模型与工具之间的循环;你决定如何保存会话,以及它怎样与你的系统协作。 -Agent 的组件由你直接提供。[Bub 应用](/zh-cn/docs/app/)建立在同一个内核上,提供配置和插件发现能力。你也可以在自有服务中[复用应用插件](/zh-cn/docs/sdk/app-plugins/),保留现有入口和调用代码。 +Agent 的组件由你直接提供。[Bub 应用](/zh-cn/docs/app/workspace/)建立在同一个内核上,提供配置和插件发现能力。你也可以在自有服务中[复用应用插件](/zh-cn/docs/sdk/app-plugins/),保留现有入口和调用代码。 ## 安装 @@ -202,6 +202,6 @@ def check_results(event: StepFinished) -> None: ## 继续构建 -- [添加工具与 CodeMode](/zh-cn/docs/sdk/tools/),让 Agent 执行动作。 +- [工具与 CodeMode](/zh-cn/docs/sdk/tools/),让 Agent 执行动作。 - [保存会话历史](/zh-cn/docs/concepts/tape/),在多轮运行之间继续工作。 - [通过 FastAPI 提供服务](/zh-cn/docs/sdk/web-service/),让其他程序通过 HTTP 调用 Agent。 From 36db78f3f15c20b958100caaaf27262e4b6f0928 Mon Sep 17 00:00:00 2001 From: 100gle Date: Wed, 21 Oct 2026 16:46:31 +0800 Subject: [PATCH 05/28] docs(sdk): split the agent guide into tasks The section used to put the whole SDK tour on one page, then follow it with pages whose titles described the work ("Reusing plugins", "Agent configuration") rather than what the reader gets. The first page was called "First agent", which read like the opening of a tutorial series. - open with Basic agent: one runnable example plus the stream it returns - move the model, prompt, and parameter reference to Configure the agent - rename app-plugins to reuse-plugins; the page is about reusing a plugin, not about the app - group the plugin page by what a plugin can do: add a tool, extend the run (model and tool calls, then the other hooks), choose how to connect, reuse BubAgent - say that run_on() and run_once() take an open Tape object, not a tape name, which run(tape=...) does - give the run_stream() signature: session_id and prompt are required - document the CodeMode name option - say how several plugins share one hook: all of them run, a duplicate tool, store, or tape context keeps the highest-priority provider, and prompt sections join from low to high priority - keep the redirect from /docs/sdk/app-plugins and /docs/build/*, so old links still land --- website/astro.config.mjs | 13 +- .../content/docs/docs/app/distribution.mdx | 2 +- website/src/content/docs/docs/app/plugins.mdx | 2 +- .../src/content/docs/docs/concepts/events.mdx | 2 +- .../docs/docs/concepts/extension-points.mdx | 2 +- .../docs/docs/integrations/storage.mdx | 2 +- .../src/content/docs/docs/reference/index.mdx | 4 +- website/src/content/docs/docs/sdk/index.md | 141 ++---------------- website/src/content/docs/docs/sdk/options.mdx | 118 +++++++++++++++ .../{app-plugins.mdx => reuse-plugins.mdx} | 24 +-- website/src/content/docs/docs/sdk/tools.mdx | 5 +- .../src/content/docs/docs/sdk/web-service.mdx | 8 +- .../docs/zh-cn/docs/app/distribution.mdx | 2 +- .../content/docs/zh-cn/docs/app/plugins.mdx | 2 +- .../docs/zh-cn/docs/concepts/events.mdx | 2 +- .../zh-cn/docs/concepts/extension-points.mdx | 2 +- .../docs/zh-cn/docs/integrations/storage.mdx | 2 +- .../docs/zh-cn/docs/reference/index.mdx | 4 +- .../src/content/docs/zh-cn/docs/sdk/index.md | 141 ++---------------- .../content/docs/zh-cn/docs/sdk/options.mdx | 118 +++++++++++++++ .../{app-plugins.mdx => reuse-plugins.mdx} | 24 +-- .../src/content/docs/zh-cn/docs/sdk/tools.mdx | 7 +- .../docs/zh-cn/docs/sdk/web-service.mdx | 8 +- 23 files changed, 319 insertions(+), 316 deletions(-) create mode 100644 website/src/content/docs/docs/sdk/options.mdx rename website/src/content/docs/docs/sdk/{app-plugins.mdx => reuse-plugins.mdx} (92%) create mode 100644 website/src/content/docs/zh-cn/docs/sdk/options.mdx rename website/src/content/docs/zh-cn/docs/sdk/{app-plugins.mdx => reuse-plugins.mdx} (92%) diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 6a32e2ea1..0ad37d296 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -42,7 +42,7 @@ export default defineConfig({ '/docs/build/distribution': '/docs/app/distribution/', '/docs/build/hooks': '/docs/app/hooks/', '/docs/build/plugins': '/docs/app/plugins/', - '/docs/build/reuse-plugins': '/docs/sdk/app-plugins/', + '/docs/build/reuse-plugins': '/docs/sdk/reuse-plugins/', '/docs/build/sdk': '/docs/sdk/', '/docs/build/skills': '/docs/app/skills/', '/docs/build/tools': '/docs/sdk/tools/', @@ -60,11 +60,12 @@ export default defineConfig({ '/docs/operate/channels/telegram': '/docs/app/channels/', '/docs/operate/configure': '/docs/app/configure/', '/docs/operate/deploy': '/docs/app/deploy/', + '/docs/sdk/app-plugins': '/docs/sdk/reuse-plugins/', '/docs/tutorials': '/docs/integrations/', '/docs/tutorials/acp-server': '/docs/integrations/acp/', '/docs/tutorials/agent-plugins': '/docs/integrations/agent-plugins/', '/docs/tutorials/fastapi': '/docs/sdk/web-service/', - '/docs/tutorials/language-extensions': '/docs/app/language-extensions/', + '/docs/tutorials/language-extensions': '/docs/app/plugins/', '/docs/tutorials/local-llama-cpp': '/docs/integrations/local-models/', '/docs/tutorials/mcp': '/docs/integrations/mcp/', '/docs/tutorials/observability': '/docs/integrations/observability/', @@ -82,7 +83,7 @@ export default defineConfig({ '/zh-cn/docs/build/distribution': '/zh-cn/docs/app/distribution/', '/zh-cn/docs/build/hooks': '/zh-cn/docs/app/hooks/', '/zh-cn/docs/build/plugins': '/zh-cn/docs/app/plugins/', - '/zh-cn/docs/build/reuse-plugins': '/zh-cn/docs/sdk/app-plugins/', + '/zh-cn/docs/build/reuse-plugins': '/zh-cn/docs/sdk/reuse-plugins/', '/zh-cn/docs/build/sdk': '/zh-cn/docs/sdk/', '/zh-cn/docs/build/skills': '/zh-cn/docs/app/skills/', '/zh-cn/docs/build/tools': '/zh-cn/docs/sdk/tools/', @@ -100,11 +101,12 @@ export default defineConfig({ '/zh-cn/docs/operate/channels/telegram': '/zh-cn/docs/app/channels/', '/zh-cn/docs/operate/configure': '/zh-cn/docs/app/configure/', '/zh-cn/docs/operate/deploy': '/zh-cn/docs/app/deploy/', + '/zh-cn/docs/sdk/app-plugins': '/zh-cn/docs/sdk/reuse-plugins/', '/zh-cn/docs/tutorials': '/zh-cn/docs/integrations/', '/zh-cn/docs/tutorials/acp-server': '/zh-cn/docs/integrations/acp/', '/zh-cn/docs/tutorials/agent-plugins': '/zh-cn/docs/integrations/agent-plugins/', '/zh-cn/docs/tutorials/fastapi': '/zh-cn/docs/sdk/web-service/', - '/zh-cn/docs/tutorials/language-extensions': '/zh-cn/docs/app/language-extensions/', + '/zh-cn/docs/tutorials/language-extensions': '/zh-cn/docs/app/plugins/', '/zh-cn/docs/tutorials/local-llama-cpp': '/zh-cn/docs/integrations/local-models/', '/zh-cn/docs/tutorials/mcp': '/zh-cn/docs/integrations/mcp/', '/zh-cn/docs/tutorials/observability': '/zh-cn/docs/integrations/observability/', @@ -258,9 +260,10 @@ export default defineConfig({ translations: { 'zh-CN': '构建你的 Bub Agent' }, items: [ 'docs/sdk', + 'docs/sdk/options', 'docs/sdk/tools', + 'docs/sdk/reuse-plugins', 'docs/sdk/web-service', - 'docs/sdk/app-plugins', ], }, { diff --git a/website/src/content/docs/docs/app/distribution.mdx b/website/src/content/docs/docs/app/distribution.mdx index ca042ba97..38866007f 100644 --- a/website/src/content/docs/docs/app/distribution.mdx +++ b/website/src/content/docs/docs/app/distribution.mdx @@ -49,7 +49,7 @@ Keep `framework.running()` open for the service's lifetime. It acquires hook-pro ## Reuse plugins in your own service -A host can use plugin-provided tools, stores, or hooks while keeping its own API and stream handling. Follow [Reuse plugins in your agent](/docs/sdk/app-plugins/) to connect those capabilities to `core.Agent`. +A host can use plugin-provided tools, stores, or hooks while keeping its own API and stream handling. Follow [Reusing plugins](/docs/sdk/reuse-plugins/) to connect those capabilities to `core.Agent`. `BubAgent` also carries the application's prompts and channel delivery conventions. Choose it when you want those application behaviors, and provide the corresponding output handling in your host. diff --git a/website/src/content/docs/docs/app/plugins.mdx b/website/src/content/docs/docs/app/plugins.mdx index b0581e1f3..1544d977d 100644 --- a/website/src/content/docs/docs/app/plugins.mdx +++ b/website/src/content/docs/docs/app/plugins.mdx @@ -7,7 +7,7 @@ sidebar: A plugin changes how the Bub app handles a task. This page builds a small echo plugin so you can check discovery and turn processing without model credentials, then covers the entry-point choices, tool registration, sharing, and the WebAssembly route for other languages. -A Python package declares an entry point in the `bub` group; Bub imports that target and registers its hook implementations. You can also reuse an existing plugin in your own service while keeping your host's entry point; see [Borrow the app runtime](/docs/sdk/app-plugins/). +A Python package declares an entry point in the `bub` group; Bub imports that target and registers its hook implementations. You can also reuse an existing plugin in your own service while keeping your host's entry point; see [Reusing plugins](/docs/sdk/reuse-plugins/). ## Create the package diff --git a/website/src/content/docs/docs/concepts/events.mdx b/website/src/content/docs/docs/concepts/events.mdx index db1af7168..860a90f6d 100644 --- a/website/src/content/docs/docs/concepts/events.mdx +++ b/website/src/content/docs/docs/concepts/events.mdx @@ -128,7 +128,7 @@ async def mark_reviewed() -> None: ## App plugins -`AgentPlugins` connects four hooks to the call events: `before_llm_call`, `after_llm_call`, `before_tool_call`, and `after_tool_call`. It connects the `continue_prompt` hook to `StepFinished`. It connects the steering inbox to `MessagesPending`. A plugin returns the values that the hook contract requires, and the adapter updates the event. `BubAgent` uses this adapter, and a core Agent can [use it too](/docs/sdk/app-plugins/#agent-plugins). +`AgentPlugins` connects four hooks to the call events: `before_llm_call`, `after_llm_call`, `before_tool_call`, and `after_tool_call`. It connects the `continue_prompt` hook to `StepFinished`. It connects the steering inbox to `MessagesPending`. A plugin returns the values that the hook contract requires, and the adapter updates the event. `BubAgent` uses this adapter, and a core Agent can [use it too](/docs/sdk/reuse-plugins/#agent-plugins). This adapter follows the plugin hook order. It catches exceptions from individual interception hooks, logs them, and skips them. To deny a call, return the correct decision. The other app hooks follow their own error contracts. diff --git a/website/src/content/docs/docs/concepts/extension-points.mdx b/website/src/content/docs/docs/concepts/extension-points.mdx index 7de6cef75..a21c7e13d 100644 --- a/website/src/content/docs/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/docs/concepts/extension-points.mdx @@ -30,4 +30,4 @@ A channel delivers an inbound envelope and sends outbound envelopes. The app use The [a turn](/docs/concepts/turn/) of the app prepares that state and the prompt before it calls the agent. A direct SDK caller gives the prompt and the state to Agent. -Choose the dependencies for the capabilities that you need. Then choose the interface for the host. A Python service can reuse app plugins and keep its own request handling and streams. [Reuse plugins in your agent](/docs/sdk/app-plugins/) shows how to use components directly and how the framework hooks fit into an embedded host. +Choose the dependencies for the capabilities that you need. Then choose the interface for the host. A Python service can reuse app plugins and keep its own request handling and streams. [Reusing plugins](/docs/sdk/reuse-plugins/) shows how to use components directly and how the framework hooks fit into an embedded host. diff --git a/website/src/content/docs/docs/integrations/storage.mdx b/website/src/content/docs/docs/integrations/storage.mdx index 6278da350..da11a600b 100644 --- a/website/src/content/docs/docs/integrations/storage.mdx +++ b/website/src/content/docs/docs/integrations/storage.mdx @@ -8,7 +8,7 @@ sidebar: Store the Bub application's conversation history in a local SQLite database with [`bub-tapestore-sqlalchemy`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-tapestore-sqlalchemy). The plugin replaces the app's default file store; you can continue using `,tape.info` and `,tape.search` to inspect records. -This guide configures the plugin for the Bub CLI through `BUB_*` settings. In your own Python program, you can construct `SQLAlchemyTapeStore` and pass it directly to `core.Agent`; see [Reuse plugins in your agent](/docs/sdk/app-plugins/). [Tape and context](/docs/concepts/tape/) explains the shared storage behavior. +This guide configures the plugin for the Bub CLI through `BUB_*` settings. In your own Python program, you can construct `SQLAlchemyTapeStore` and pass it directly to `core.Agent`; see [Reusing plugins](/docs/sdk/reuse-plugins/). [Tape and context](/docs/concepts/tape/) explains the shared storage behavior. ## Before you begin diff --git a/website/src/content/docs/docs/reference/index.mdx b/website/src/content/docs/docs/reference/index.mdx index c5ead7d79..a1e7e46ec 100644 --- a/website/src/content/docs/docs/reference/index.mdx +++ b/website/src/content/docs/docs/reference/index.mdx @@ -11,9 +11,9 @@ Look up the interface used by your code. The Agent interfaces and application ex | Interface | Reference | | --- | --- | -| Agent construction and model clients | [Agent parameters](/docs/sdk/#agent-parameters) and [models](/docs/sdk/#models) | +| Agent construction and model clients | [Agent parameters](/docs/sdk/options/#agent-parameters) and [models](/docs/sdk/options/#models) | | Runs and stream ownership | [Streams and lifecycle](/docs/sdk/#streams-and-lifecycle) | -| Prompt sections and loop customization | [System prompt](/docs/sdk/#system-prompt) and [customize the loop](/docs/sdk/#customize-the-loop) | +| Prompt sections and loop customization | [System prompt](/docs/sdk/options/#system-prompt) and [customize the loop](/docs/sdk/options/#customize-the-loop) | | Tools and CodeMode | [Tools](/docs/sdk/tools/) | | Tape, storage, and context | [Tape and context](/docs/concepts/tape/) | | Event delivery and decisions | [Agent events](/docs/concepts/events/) | diff --git a/website/src/content/docs/docs/sdk/index.md b/website/src/content/docs/docs/sdk/index.md index 9a6b4d752..2aef71ad4 100644 --- a/website/src/content/docs/docs/sdk/index.md +++ b/website/src/content/docs/docs/sdk/index.md @@ -1,29 +1,17 @@ --- -title: Build your own agent -description: Choose a model, add tools, and run a Bub agent inside your Python program. +title: Basic agent +description: Create a basic agent in your own Python program and control the stream it produces. sidebar: order: 0 --- Choose a model, add tools, and connect the agent to your program. `bub.core.Agent` runs the model and tool loop while you decide how to store conversations and manage the surrounding application. -You supply the agent's components directly. The [Bub application](/docs/app/workspace/) builds on the same core and adds configuration and plugin discovery. You can also [reuse application plugins](/docs/sdk/app-plugins/) in your own service while keeping its existing entry points and calling code. - -## Install - -Use Python 3.12+ and add Bub to your project: - -```bash -uv init my-bub -cd my-bub -uv add bub -``` - -The base `bub` package installs the dependencies needed by the agent. Add `bub[app]` when you want to use the Bub application's CLI, plugins, or channels. See [Installation](/docs/getting-started/install/) for other setup options. +This page assumes Bub is installed for the agent path; see [Install Bub](/docs/getting-started/install/#agent-install). The [Bub application](/docs/app/workspace/) builds on the same core and adds configuration and plugin discovery. -## Run your first agent +## Run the example Save this as `sdk_example.py` in your project: @@ -86,122 +74,13 @@ Run the program again with the same `tape` name to continue the conversation sto Keep the stream inside `async with`. Leaving the block closes it after normal completion, an exception, or an early `break`. Closing also releases the open model stream and merges the run's tape fork. Records already written are kept even when the run fails; see [Forks and partial results](/docs/concepts/tape/#forks). -If another component will consume the stream, `await agent.start(...)` returns it with the same arguments. That component must close it, including when it never starts iteration. Prefer `run()` when you own the consuming code. +If another component will consume the stream, `await agent.start(...)` takes the same arguments and returns the stream. That component must close it, including when it never starts iteration. Prefer `run()` when you own the consuming code. Stream events include `text`, `reasoning`, `tool_call`, `tool_result`, `usage`, `error`, and `final`. Handle error events and exceptions from iteration. For internal lifecycle notifications and call interception, use [agent events](/docs/concepts/events/). - - -## Choose a model - -Pass a Republic chat model to `Agent(model=...)` for a default, or to `agent.run(model=...)` for one run. A run that needs a model raises `ValueError` when neither is supplied. You own the model's lifetime, so open it with `async with` around the runs that use it. - -Republic accepts provider credentials and endpoints directly: - -```python -async with republic.get_model( - "openai:gpt-5-mini", api_key="...", api_base="https://example.test/v1" -) as model: - agent = Agent(model=model) - async with agent.run("Hello", tape="example", options={"max_tokens": 4096}) as stream: - async for event in stream: - print(event.kind, event.data) -``` - -`options` are Republic chat options sent with each model call of the run. The agent adds the tools for that step. Configure this client through Republic's arguments or environment variables; `BUB_*` settings belong to the Bub application. - -For your own fallback, credential, or timeout policy, implement `bub.core.ModelClient`: - -| Member | Contract | -| --- | --- | -| `spec` | Default `provider:model` string. | -| `max_tokens` | Default output token limit, or `None`. | -| `open_stream(messages, *, spec, options)` | Async context manager yielding `(provider_name, republic_stream)`. Messages are `republic.Message` objects; options include the step's tools. | - -A `LLMCallStarted` handler can change the requested model. With a Republic chat model, a change within the same provider reuses its client; a different provider uses a client configured from Republic's environment. Supply a `ModelClient` when this policy needs to differ. - - - -## Set the instructions - -`system_prompt` accepts text, a function, or an ordered list of either. Functions receive a `PromptContext` and may be synchronous or asynchronous. The agent renders the sections once per run, before its first model step, omits empty results, and joins the rest with blank lines. Later steps of the run reuse that text. - -```python -from bub.core import PromptContext - - -@agent.system_prompt.add -def customer(context: PromptContext) -> str | None: - if (name := context.state.get("user")) is not None: - return f"You are helping {name}." - return None -``` - -`PromptContext` carries the run's prompt, model-facing tools, model spec, tape, turn state, and agent. `available_tools` lists the agent's registered tools. Remove a section with `agent.system_prompt.remove(section)`. - - - - -The `state` passed to a run is a mutable dictionary shared by tools, prompt sections, and event handlers. You decide which values to save and how to restore them for another run. See [Turn state](/docs/concepts/tape/#state) for what to keep between runs. - - - - - -## Configure the agent - -| Parameter | Default and purpose | -| --- | --- | -| `model` | `None`. Default Republic chat model or `ModelClient`. | -| `store` | A new `InMemoryTapeStore`. Accepts synchronous or asynchronous tape stores. | -| `tools` | Empty. Accepts `Tool` objects and Republic provider tools such as `republic.tools.WebSearch()`. | -| `system_prompt` | Empty. Text or callable sections, rendered per step. | -| `context` | `default_tape_context()`. Selects tape entries for model messages. | -| `sidecars` | Empty. Extra storage capabilities mounted alongside the tape. | -| `max_steps` | `sys.maxsize`. Maximum model steps in a run. | -| `max_tool_concurrency` | `None`, so calls from one model response may all run concurrently. Set `1` to run them sequentially. Your application coordinates separate runs. | -| `name` | `"agent"`. Name used in agent trace spans. | -| `command_prefix` | `None`. Set a prefix to invoke tools through text commands without a model. | - -The agent keeps supplied `Tool` objects in `agent.tools` by name and provider tools in `agent.builtin_tools`. You select its tools explicitly when constructing or updating the agent. - - - -## Customize how a step is prepared - -Subclass `Agent` and override `prepare_tools(tape, prompt)` when selecting tools needs application logic. By default it offers the supplied tools except those marked `exposure="command"`. The agent prepares tools once per run, before its first model step, and offers the same tools in every step; a tool call outside them is resolved with a fresh `prepare_tools(tape, None)`, so tools that become available during the run are still callable. The tape's `context.state` is the run's state. - -Handle two loop events to send messages that arrive during a run or to steer a continuing run: - -| Event | When | Control | -| --- | --- | --- | -| `MessagesPending` | Each step builds its model request, and a step ends without tool calls. | Append contents to `event.messages`; they are sent before the next prompt and recorded with it. Messages supplied when a step ends continue the run. | -| `StepFinished` | A step ends. With `status == "continue"`, the run takes another step. | Set `next_prompt` to give that step a user message; `None` continues the conversation as is. `event.prompt` is this step's prompt and `event.stream` holds the run's usage. | - -```python -from bub.core.events import MessagesPending, StepFinished - -inbox: dict[str, list[str]] = {} - - -@agent.events.on(MessagesPending) -def send_queued(event: MessagesPending) -> None: - event.messages.extend([text] for text in inbox.pop(event.tape.name, [])) - - -@agent.events.on(StepFinished) -def check_results(event: StepFinished) -> None: - if event.status == "continue": - event.next_prompt = ["Check the tool results before answering."] -``` - -For a tape whose lifecycle you manage, open `agent.session(name, state=...)` and use `agent.run_on(tape, prompt, model=...)`. It returns a stream you must close. `agent.run_once(prompt, model=..., tape=...)` yields one model step when you need to drive the loop yourself. - - - - -## Keep building +## Next steps -- [Add tools and CodeMode](/docs/sdk/tools/) to give your agent actions. -- [Store conversation history](/docs/concepts/tape/) to continue work across runs. -- [Serve your agent with FastAPI](/docs/sdk/web-service/) when another program needs an HTTP endpoint. +- [Configure the agent](/docs/sdk/options/): the model, the instructions, and the rest of the `Agent` parameters. +- [Tools and CodeMode](/docs/sdk/tools/): give the agent actions. +- [Reusing plugins](/docs/sdk/reuse-plugins/): bring existing plugin tools and hooks into your agent. +- [Example: FastAPI service](/docs/sdk/web-service/): put the agent behind an HTTP endpoint. diff --git a/website/src/content/docs/docs/sdk/options.mdx b/website/src/content/docs/docs/sdk/options.mdx new file mode 100644 index 000000000..11ed823e7 --- /dev/null +++ b/website/src/content/docs/docs/sdk/options.mdx @@ -0,0 +1,118 @@ +--- +title: Configure the agent +description: Choose the model, the instructions, and the other options that shape a run. +sidebar: + order: 1 +--- + +These options shape how an agent runs. Start from the [basic agent](/docs/sdk/) example, then change the options you need. + + + +## Choose a model + +Pass a Republic chat model to `Agent(model=...)` for a default, or to `agent.run(model=...)` for one run. A run that needs a model raises `ValueError` when neither is supplied. You own the model's lifetime, so open it with `async with` around the runs that use it. + +Republic accepts provider credentials and endpoints directly: + +```python +async with republic.get_model( + "openai:gpt-5-mini", api_key="...", api_base="https://example.test/v1" +) as model: + agent = Agent(model=model) + async with agent.run("Hello", tape="example", options={"max_tokens": 4096}) as stream: + async for event in stream: + print(event.kind, event.data) +``` + +`options` are Republic chat options sent with each model call of the run. The agent adds the tools for that step. Configure the model through Republic's arguments or environment variables; `BUB_*` settings belong to the Bub application. + +For your own fallback, credential, or timeout policy, implement `bub.core.ModelClient`: + +| Member | Contract | +| --- | --- | +| `spec` | Default `provider:model` string. | +| `max_tokens` | Default output token limit, or `None`. | +| `open_stream(messages, *, spec, options)` | Async context manager yielding `(provider_name, republic_stream)`. Messages are `republic.Message` objects; options include the step's tools. | + +A `LLMCallStarted` handler can change the requested model. With a Republic chat model, a change within the same provider reuses its client; a different provider uses a client configured from Republic's environment. Supply a `ModelClient` when this policy needs to differ. + + + +## Set the instructions + +`system_prompt` accepts text, a function, or an ordered list of either. Functions receive a `PromptContext` and may be synchronous or asynchronous. The agent renders the sections once per run, before its first model step, omits empty results, and joins the rest with blank lines. Later steps of the run reuse that text. + +```python +from bub.core import PromptContext + + +@agent.system_prompt.add +def customer(context: PromptContext) -> str | None: + if (name := context.state.get("user")) is not None: + return f"You are helping {name}." + return None +``` + +`PromptContext` carries the run's prompt, model-facing tools, model spec, tape, turn state, and agent. `available_tools` lists the agent's registered tools. Remove a section with `agent.system_prompt.remove(section)`. + + + + +The `state` passed to a run is a mutable dictionary shared by tools, prompt sections, and event handlers. You decide which values to save and how to restore them for another run. See [Turn state](/docs/concepts/tape/#state) for what to keep between runs. + + + + + +## Set the other parameters + +| Parameter | Default and purpose | +| --- | --- | +| `model` | `None`. Default Republic chat model or `ModelClient`. | +| `store` | A new `InMemoryTapeStore`. Accepts synchronous or asynchronous tape stores. | +| `tools` | Empty. Accepts `Tool` objects and Republic provider tools such as `republic.tools.WebSearch()`. | +| `system_prompt` | Empty. Text or callable sections, rendered once per run. | +| `context` | `default_tape_context()`. Selects tape entries for model messages. | +| `sidecars` | Empty. Extra storage capabilities mounted alongside the tape. | +| `max_steps` | `sys.maxsize`. Maximum model steps in a run. | +| `max_tool_concurrency` | `None`, so calls from one model response may all run concurrently. Set `1` to run them sequentially. Your application coordinates separate runs. | +| `name` | `"agent"`. Name used in agent trace spans. | +| `command_prefix` | `None`. Set a prefix to invoke tools through text commands without a model. | + +The agent keeps supplied `Tool` objects in `agent.tools` by name and provider tools in `agent.builtin_tools`. You select its tools explicitly when constructing or updating the agent. + + + +## Customize how a step is prepared + +Subclass `Agent` and override `prepare_tools(tape, prompt)` when selecting tools needs application logic. By default it offers the supplied tools except those marked `exposure="command"`. The agent prepares tools once per run, before its first model step, and offers the same tools in every step; a tool call outside them is resolved with a fresh `prepare_tools(tape, None)`, so tools that become available during the run are still callable. The tape's `context.state` is the run's state. + +Handle two loop events to send messages that arrive during a run or to steer a continuing run: + +| Event | When | Control | +| --- | --- | --- | +| `MessagesPending` | Each step builds its model request, and a step ends without tool calls. | Append contents to `event.messages`; they are sent before the next prompt and recorded with it. Messages supplied when a step ends continue the run. | +| `StepFinished` | A step ends. With `status == "continue"`, the run takes another step. | Set `next_prompt` to give that step a user message; `None` continues the conversation as is. `event.prompt` is this step's prompt and `event.stream` holds the run's usage. | + +```python +from bub.core.events import MessagesPending, StepFinished + +inbox: dict[str, list[str]] = {} + + +@agent.events.on(MessagesPending) +def send_queued(event: MessagesPending) -> None: + event.messages.extend([text] for text in inbox.pop(event.tape.name, [])) + + +@agent.events.on(StepFinished) +def check_results(event: StepFinished) -> None: + if event.status == "continue": + event.next_prompt = ["Check the tool results before answering."] +``` + +`agent.session(name, state=...)` opens a tape whose lifecycle you manage. Pass that `Tape` object, not its name, to `agent.run_on(tape, prompt, model=...)` or to `agent.run_once(prompt, model=..., tape=)`. `run_on()` returns a stream you must close. `run_once()` yields one model step when you drive the loop yourself. + + + diff --git a/website/src/content/docs/docs/sdk/app-plugins.mdx b/website/src/content/docs/docs/sdk/reuse-plugins.mdx similarity index 92% rename from website/src/content/docs/docs/sdk/app-plugins.mdx rename to website/src/content/docs/docs/sdk/reuse-plugins.mdx index 9b8211c10..a0cb6f53c 100644 --- a/website/src/content/docs/docs/sdk/app-plugins.mdx +++ b/website/src/content/docs/docs/sdk/reuse-plugins.mdx @@ -1,8 +1,8 @@ --- -title: Reuse application plugins +title: Reusing plugins description: Bring plugin tools and hooks into your own agent while keeping your existing service and calling code. sidebar: - order: 1 + order: 4 --- Use Bub's application plugins in your existing Python service. Your host keeps its HTTP routes, task handlers, and output handling. Choose the agent and runtime according to the application behavior you want to reuse. @@ -11,7 +11,7 @@ Use Bub's application plugins in your existing Python service. Your host keeps i With `core.Agent`, you choose the components and connect the hooks yourself. The examples below take a search tool from one plugin and adapt the call hooks of another. Each example covers that plugin's dependencies; background services need their own startup and shutdown. -## Add a search plugin to your agent +## Add a search tool [`bub-web-search`](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-web-search) exposes a search tool that works with `core.Agent`. Install it in the environment that runs your service. These examples use plugin packages from the `dev` branch of `bub-contrib`: @@ -70,7 +70,11 @@ For an existing Agent, add the returned tool with `agent.tools[search.name] = se `register_tools()` also updates the process-wide tool registry, so do this setup during startup. An Agent uses the tools you explicitly pass or add to it. This example takes the returned search tool; add the package's other tools, such as Jina's `web.read`, separately if needed. -## Advanced: connect one plugin's call hooks +## Extend the run + +`AgentPlugins` connects a plugin's hooks to your Agent's events. + +### Model and tool calls [`bub-context-budget`](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-context-budget) runs before and after model calls. When its estimate crosses a threshold, it adds an instruction asking the model to call `tape.handoff`. Give the Agent that tool and subscribe the plugin's hooks to its events. @@ -109,9 +113,9 @@ The adapter catches ordinary plugin exceptions and skips the failed hook. A hook -## Connect a plugin's other agent-loop hooks +### Other hooks -`AgentPlugins` connects every hook that shapes the agent loop, not only call interception. Each hook maps to a core Agent extension point: +The other hooks decide what the run has. Each hook maps to a core Agent extension point: | Hook | Core Agent extension point | | --- | --- | @@ -137,9 +141,9 @@ async with plugins.running(): `components()` leaves out what no hook provides, so the Agent keeps its defaults; arguments you pass yourself take their place. A hook-provided store and steering inbox exist only inside `plugins.running()`, which enters `framework.running()`. Attach before registering your own handlers so they see the hooks' changes. -Message-pipeline hooks, such as `build_prompt`, `run_model`, `save_state`, `render_outbound`, and `dispatch_outbound`, are not part of the agent loop and only run in `process_inbound()`. `plugins=[...]` registers exactly the listed plugins. `AgentPlugins()` without arguments loads Bub's builtin hooks and the installed plugins, as the `bub` command does. Pass `framework` to use an existing framework's hooks; `plugins` given with it are registered on it. +Message-pipeline hooks, such as `build_prompt`, `run_model`, `save_state`, `render_outbound`, and `dispatch_outbound`, are not part of the agent loop and only run in `process_inbound()`. `plugins=[...]` registers exactly the listed plugins. When several plugins implement the same hook, all of them run: duplicate tools, stores, and tape contexts keep the highest-priority provider, and prompt sections join from low to high priority. `AgentPlugins()` without arguments loads Bub's builtin hooks and the installed plugins, as the `bub` command does. Pass `framework` to use an existing framework's hooks; `plugins` given with it are registered on it. -## Choose the connection each plugin needs +## Choose how to connect The plugin's implementation determines what your host must provide: @@ -156,9 +160,9 @@ For MCP, follow the plugin's [embedding example](https://github.com/bubbuild/bub -## Reuse Bub's application defaults +## Reuse BubAgent -`BubAgent` combines the core loop with Bub settings, skills, tool policies, and plugin hooks. A host can load installed plugins with `framework.load_hooks()`, enter `framework.running()`, then construct a `BubAgent`. Its `run_stream()` returns a stream for the host to consume and close. +`BubAgent` combines the core loop with Bub settings, skills, tool policies, and plugin hooks. A host can load installed plugins with `framework.load_hooks()`, enter `framework.running()`, then construct `BubAgent(framework)`. Its `run_stream(*, session_id, prompt, state=None, ...)` returns a stream for the host to consume and close. Choose this when you also want those application defaults. The builtin system prompt does not ask the agent to reply through channel skills; the channel manager behind `bub gateway` and `bub chat` registers a `system_prompt` hook that adds the instruction only to the turns it runs. A service that calls `process_inbound()` itself can register its own `system_prompt` hook to describe how its replies are delivered. Replacing `agent.system_prompt` replaces all its sections, including plugin, skills, deferred-tool, and CodeMode instructions. diff --git a/website/src/content/docs/docs/sdk/tools.mdx b/website/src/content/docs/docs/sdk/tools.mdx index ef85a0417..91f787304 100644 --- a/website/src/content/docs/docs/sdk/tools.mdx +++ b/website/src/content/docs/docs/sdk/tools.mdx @@ -31,7 +31,7 @@ agent = Agent(tools=[add_tool]) The runtime keeps the name `math.add`; model-facing names replace dots with underscores, so the model sees `math_add`. Use `description=` to supply a description explicitly. -Plain `Agent` offers all supplied tools except those marked `exposure="command"`. Deferred loading, the `allowed_tools` state value, and the `state["code_mode"]` switch are [BubAgent policies](/docs/app/tools/). To choose tools per run in your own agent, override [`prepare_tools()`](/docs/sdk/#customize-the-loop). +Plain `Agent` offers all supplied tools except those marked `exposure="command"`. Deferred loading, the `allowed_tools` state value, and the `state["code_mode"]` switch are [BubAgent policies](/docs/app/tools/). To choose tools per run in your own agent, override [`prepare_tools()`](/docs/sdk/options/#customize-the-loop). @@ -103,7 +103,7 @@ agent = Agent( ) ``` -Run this agent with the model lifecycle shown in the [SDK guide](/docs/sdk/#models). The model can call `run_code` with code such as: +Run this agent with the model lifecycle shown in [Configure the agent](/docs/sdk/options/#models). The model can call `run_code` with code such as: ```python order = await tools.lookup_order(order_id="A123") @@ -121,6 +121,7 @@ Inside `run_code`, tool names use underscores, calls take keyword arguments, and | `models` | Default `provider:model` for Republic calls made by the code, or a resolver returning a model spec and client options. Without it, code must supply a spec and Republic uses environment credentials. | | `stub_dir` | Optional directory for the generated Python tool definitions. By default, the definitions appear in the system prompt. A file-based stub requires an agent that can read that file. | | `media_tools` | `True` by default. Adds `attach_image`, `attach_audio`, and `attach_video` for returning media. | +| `name` | Name of the `run_code` tool. Defaults to `run_code`. | The code receives a Republic bridge for chat, embedding, and decision model calls. Requests go through the host's model resolver. The host handles provider credentials for these calls. diff --git a/website/src/content/docs/docs/sdk/web-service.mdx b/website/src/content/docs/docs/sdk/web-service.mdx index f0ceed19f..eab9eee71 100644 --- a/website/src/content/docs/docs/sdk/web-service.mdx +++ b/website/src/content/docs/docs/sdk/web-service.mdx @@ -1,8 +1,8 @@ --- -title: Serve your agent with FastAPI +title: "Example: FastAPI service" description: Give your Agent an HTTP endpoint and keep a separate conversation for each session. sidebar: - order: 7 + order: 5 --- Give your Agent an HTTP endpoint so another program can send it work. Each request names a session; requests with the same name continue a conversation stored on disk. @@ -19,9 +19,7 @@ cd bub-api uv add bub fastapi uvicorn ``` -This service uses the base SDK dependencies together with FastAPI and Uvicorn. - -To add an existing Bub plugin, follow [Reuse application plugins](/docs/sdk/app-plugins/). You can add its tools or selected hooks while keeping the endpoint and request lifecycle below. +To add an existing Bub plugin, follow [Reusing plugins](/docs/sdk/reuse-plugins/). You can add its tools or selected hooks while keeping the endpoint and request lifecycle below. ## Write the service diff --git a/website/src/content/docs/zh-cn/docs/app/distribution.mdx b/website/src/content/docs/zh-cn/docs/app/distribution.mdx index 2312f6c9b..80e31dc17 100644 --- a/website/src/content/docs/zh-cn/docs/app/distribution.mdx +++ b/website/src/content/docs/zh-cn/docs/app/distribution.mdx @@ -49,7 +49,7 @@ asyncio.run(main()) ## 在自己的服务中复用插件 -宿主可以使用插件提供的工具、存储或 hooks,同时保留自己的 API 和流处理方式。按[在 Agent 中复用插件](/zh-cn/docs/sdk/app-plugins/)中的步骤,把这些能力接到 `core.Agent` 上。 +宿主可以使用插件提供的工具、存储或 hooks,同时保留自己的 API 和流处理方式。按[复用插件](/zh-cn/docs/sdk/reuse-plugins/)中的步骤,把这些能力接到 `core.Agent` 上。 `BubAgent` 还包含应用的提示词和渠道发送约定。需要这些应用行为时,可以选择它,并在宿主中接好对应的输出处理。 diff --git a/website/src/content/docs/zh-cn/docs/app/plugins.mdx b/website/src/content/docs/zh-cn/docs/app/plugins.mdx index 80eebc413..1f8253ad1 100644 --- a/website/src/content/docs/zh-cn/docs/app/plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/app/plugins.mdx @@ -7,7 +7,7 @@ sidebar: 插件可以改变 Bub 应用处理任务的方式。本页先带你写一个回声插件,无需模型凭据就能检查插件发现和轮次处理,再说明入口点的几种形式、工具注册、分享方式,以及用其他语言实现的办法。 -Python 包在 `bub` 入口点组中声明目标,Bub 导入这个目标,并注册其中的 hook 实现。你也可以在自己的服务中复用现有插件,继续使用宿主自己的调用入口;参见[借用应用运行时](/zh-cn/docs/sdk/app-plugins/)。 +Python 包在 `bub` 入口点组中声明目标,Bub 导入这个目标,并注册其中的 hook 实现。你也可以在自己的服务中复用现有插件,继续使用宿主自己的调用入口;参见[复用插件](/zh-cn/docs/sdk/reuse-plugins/)。 ## 创建包 diff --git a/website/src/content/docs/zh-cn/docs/concepts/events.mdx b/website/src/content/docs/zh-cn/docs/concepts/events.mdx index 5f4531439..0e53a7539 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/events.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/events.mdx @@ -128,7 +128,7 @@ async def mark_reviewed() -> None: ## 应用插件 -`AgentPlugins` 把四个 hook 接到调用事件上:`before_llm_call`、`after_llm_call`、`before_tool_call`、`after_tool_call`。它把 `continue_prompt` 接到 `StepFinished`,把 steering inbox 接到 `MessagesPending`。插件按 hook 的要求返回值,适配器去改对应的事件。`BubAgent` 用的就是这个适配器,核心 Agent [也可以这么用](/zh-cn/docs/sdk/app-plugins/#agent-plugins)。 +`AgentPlugins` 把四个 hook 接到调用事件上:`before_llm_call`、`after_llm_call`、`before_tool_call`、`after_tool_call`。它把 `continue_prompt` 接到 `StepFinished`,把 steering inbox 接到 `MessagesPending`。插件按 hook 的要求返回值,适配器去改对应的事件。`BubAgent` 用的就是这个适配器,核心 Agent [也可以这么用](/zh-cn/docs/sdk/reuse-plugins/#agent-plugins)。 这个适配器遵循插件 hook 的顺序。单个拦截 hook 抛异常时,它接住、记日志、跳过。要拒绝一次调用,就返回对应的 decision。其他应用 hooks 按各自的出错规则处理。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx index ca0f78238..10b54cc9d 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx @@ -30,4 +30,4 @@ agent 也可以用一条[文本命令](/zh-cn/docs/sdk/tools/#commands)调工具 [轮次](/zh-cn/docs/concepts/turn/)先把状态和 prompt 准备好,再调 agent。直接用 SDK 的人,自己把 prompt 和 state 交给 Agent。 -先按要用的能力选依赖,再按宿主选调用入口。一个 Python 服务可以复用应用插件,同时保留自己的请求处理和流。[在 Agent 里复用插件](/zh-cn/docs/sdk/app-plugins/)讲怎么直接用组件,也讲 framework hooks 在嵌入的宿主里怎么接。 +先按要用的能力选依赖,再按宿主选调用入口。一个 Python 服务可以复用应用插件,同时保留自己的请求处理和流。[复用插件](/zh-cn/docs/sdk/reuse-plugins/)讲怎么直接用组件,也讲 framework hooks 在嵌入的宿主里怎么接。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/storage.mdx b/website/src/content/docs/zh-cn/docs/integrations/storage.mdx index ab2498640..bf1929531 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/storage.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/storage.mdx @@ -8,7 +8,7 @@ sidebar: 通过 [`bub-tapestore-sqlalchemy`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-tapestore-sqlalchemy),把 Bub 应用的会话历史存入本地 SQLite 数据库。插件替换应用默认的文件存储,你仍可用 `,tape.info` 和 `,tape.search` 检查记录。 -本指南通过 `BUB_*` 设置为 Bub CLI 配置插件。在自己的 Python 程序中,可以构造 `SQLAlchemyTapeStore` 并直接传给 `core.Agent`,具体用法见[在 Agent 中复用插件](/zh-cn/docs/sdk/app-plugins/)。[Tape 与上下文](/zh-cn/docs/concepts/tape/)说明两种用法共用的存储行为。 +本指南通过 `BUB_*` 设置为 Bub CLI 配置插件。在自己的 Python 程序中,可以构造 `SQLAlchemyTapeStore` 并直接传给 `core.Agent`,具体用法见[复用插件](/zh-cn/docs/sdk/reuse-plugins/)。[Tape 与上下文](/zh-cn/docs/concepts/tape/)说明两种用法共用的存储行为。 ## 前置条件 diff --git a/website/src/content/docs/zh-cn/docs/reference/index.mdx b/website/src/content/docs/zh-cn/docs/reference/index.mdx index 6d8e543c4..bd2cb5f51 100644 --- a/website/src/content/docs/zh-cn/docs/reference/index.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/index.mdx @@ -11,9 +11,9 @@ description: 查阅 Agent 接口、共享能力契约和 Bub 应用 API。 | 接口 | 参考 | | --- | --- | -| Agent 构造与模型客户端 | [Agent 参数](/zh-cn/docs/sdk/#agent-parameters)与[模型](/zh-cn/docs/sdk/#models) | +| Agent 构造与模型客户端 | [Agent 参数](/zh-cn/docs/sdk/options/#agent-parameters)与[模型](/zh-cn/docs/sdk/options/#models) | | 运行与流的生命周期 | [流与生命周期](/zh-cn/docs/sdk/#streams-and-lifecycle) | -| 提示词片段与循环定制 | [系统提示词](/zh-cn/docs/sdk/#system-prompt)与[定制循环](/zh-cn/docs/sdk/#customize-the-loop) | +| 提示词片段与循环定制 | [系统提示词](/zh-cn/docs/sdk/options/#system-prompt)与[定制循环](/zh-cn/docs/sdk/options/#customize-the-loop) | | 工具与 CodeMode | [工具](/zh-cn/docs/sdk/tools/) | | Tape、存储与上下文 | [Tape 与上下文](/zh-cn/docs/concepts/tape/) | | 事件分发与执行决策 | [Agent 事件](/zh-cn/docs/concepts/events/) | diff --git a/website/src/content/docs/zh-cn/docs/sdk/index.md b/website/src/content/docs/zh-cn/docs/sdk/index.md index 6931e7ce4..9ee77c675 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/index.md +++ b/website/src/content/docs/zh-cn/docs/sdk/index.md @@ -1,29 +1,17 @@ --- -title: 构建自己的 Agent -description: 选择模型、添加工具,把 Bub Agent 接入自己的 Python 程序。 +title: 基础 Agent +description: 在一个最小的 Python 程序里跑起 Agent,并控制它产生的输出流。 sidebar: order: 0 --- 选择模型,加入工具,把 Agent 接到你的程序里。`bub.core.Agent` 负责模型与工具之间的循环;你决定如何保存会话,以及它怎样与你的系统协作。 -Agent 的组件由你直接提供。[Bub 应用](/zh-cn/docs/app/workspace/)建立在同一个内核上,提供配置和插件发现能力。你也可以在自有服务中[复用应用插件](/zh-cn/docs/sdk/app-plugins/),保留现有入口和调用代码。 - -## 安装 - -使用 Python 3.12+,将 Bub 加入项目: - -```bash -uv init my-bub -cd my-bub -uv add bub -``` - -基础 `bub` 包安装 Agent 所需的依赖。需要 Bub 应用的 CLI、插件或渠道时,再使用 `bub[app]`。其他环境设置方式见[安装指南](/zh-cn/docs/getting-started/install/)。 +这一页假设你已经按 Agent 的方式装好 Bub,见[安装 Bub](/zh-cn/docs/getting-started/install/#agent-install)。[Bub 应用](/zh-cn/docs/app/workspace/)建立在同一个内核上,额外提供配置和插件发现。 -## 运行第一个 Agent +## 运行示例 在项目中保存为 `sdk_example.py`: @@ -84,124 +72,15 @@ uv run python sdk_example.py `agent.run()` 返回一个流,迭代这个流会推进执行:调用模型、执行模型请求的工具,再次调用模型,直到模型回答且不再请求工具。一个 `final` 事件表示一个模型步骤结束;完整消费流,才能完成这一轮运行。 -用 `async with` 管理流。无论正常结束、抛出异常,还是提前 `break`,退出代码块都会关闭流、释放打开的模型流,并合并本轮会话记录。运行失败也会保留已经写入的记录,详见[分支与部分结果](/zh-cn/docs/concepts/tape/#forks)。 +用 `async with` 管理流。无论正常结束、抛出异常,还是提前 `break`,退出代码块都会关闭流、释放打开的模型流,并合并本轮运行的 Tape 分支。运行失败也会保留已经写入的记录,详见[分支与部分结果](/zh-cn/docs/concepts/tape/#forks)。 如果另一个组件负责消费输出,可以用参数相同的 `await agent.start(...)` 取得流。接收它的组件必须负责关闭,包括尚未开始迭代的情况。自己消费输出时,优先使用 `run()`。 流事件包括 `text`、`reasoning`、`tool_call`、`tool_result`、`usage`、`error` 和 `final`。需要同时处理错误事件和迭代时抛出的异常。观察内部生命周期或拦截调用,请使用 [Agent 事件](/zh-cn/docs/concepts/events/)。 - - -## 选择模型 - -将 Republic chat model 传给 `Agent(model=...)`,可以设置默认模型;传给 `agent.run(model=...)`,则只用于当前运行。如果运行需要模型,而两处都没有提供,就会抛出 `ValueError`。模型的生命周期由你管理,请用 `async with` 包住使用它的运行过程。 - -Republic 可以直接接收凭据和服务地址: - -```python -async with republic.get_model( - "openai:gpt-5-mini", api_key="...", api_base="https://example.test/v1" -) as model: - agent = Agent(model=model) - async with agent.run("Hello", tape="example", options={"max_tokens": 4096}) as stream: - async for event in stream: - print(event.kind, event.data) -``` - -`options` 是每次模型调用使用的 Republic chat options,Agent 会补入当前步骤的工具。这个客户端通过 Republic 参数或环境变量配置;`BUB_*` 设置用于 Bub 应用。 - -如果需要自己的模型回退、凭据选择或超时策略,可以实现 `bub.core.ModelClient`: - -| 成员 | 要求 | -| --- | --- | -| `spec` | 默认的 `provider:model` 字符串。 | -| `max_tokens` | 默认输出 token 上限,或 `None`。 | -| `open_stream(messages, *, spec, options)` | 异步上下文管理器,产出 `(provider_name, republic_stream)`。messages 是 `republic.Message` 对象,options 包含本步骤的工具。 | - -`LLMCallStarted` 处理器可以更改请求的模型。使用 Republic chat model 时,同一供应商内的切换复用当前客户端;切换到其他供应商会读取 Republic 环境配置创建客户端。需要不同策略时,提供自己的 `ModelClient`。 - - - -## 设置指令 - -`system_prompt` 接收文本、函数,或按顺序排列的两者。函数接收 `PromptContext`,支持同步和异步。Agent 在每轮运行的第一个模型步骤前渲染一次这些片段,忽略空结果,再用空行拼接;同一轮运行的后续步骤复用这段文本。 - -```python -from bub.core import PromptContext - - -@agent.system_prompt.add -def customer(context: PromptContext) -> str | None: - if (name := context.state.get("user")) is not None: - return f"You are helping {name}." - return None -``` - -`PromptContext` 包含本轮运行的 prompt、提供给模型的工具、模型标识、Tape、本轮状态和 Agent。`available_tools` 列出 Agent 注册的工具。使用 `agent.system_prompt.remove(section)` 可以移除片段。 - - - - -传给运行的 `state` 是可变字典,由工具、提示词片段和事件处理器共用。哪些值需要保存、下次运行时如何恢复,由你决定,详见[本轮状态](/zh-cn/docs/concepts/tape/#state)。 - - - - - -## Agent 参数 - -| 参数 | 默认值与用途 | -| --- | --- | -| `model` | `None`。默认 Republic chat model 或 `ModelClient`。 | -| `store` | 新建的 `InMemoryTapeStore`。也可以传入同步或异步存储。 | -| `tools` | 空。接收 `Tool` 对象,以及 `republic.tools.WebSearch()` 等供应商执行的工具。 | -| `system_prompt` | 空。每个步骤渲染的文本或函数片段。 | -| `context` | `default_tape_context()`。选择用于模型消息的会话记录。 | -| `sidecars` | 空。与主 Tape 一起管理的附加存储能力。 | -| `max_steps` | `sys.maxsize`。一轮运行的模型步骤上限。 | -| `max_tool_concurrency` | `None`,同一次模型响应中的工具调用可以全部并发。设为 `1` 时依次执行。不同运行之间的并发由应用协调。 | -| `name` | `"agent"`。Agent trace span 使用的名称。 | -| `command_prefix` | `None`。设置前缀后,可以通过文本命令直接调用工具。 | - -传入的 `Tool` 按名称保存在 `agent.tools` 中,供应商工具保存在 `agent.builtin_tools` 中。构造或更新 Agent 时,由你显式选择这些工具。 - - - -## 定制步骤准备方式 - -当工具选择需要自己的业务逻辑时,可以继承 `Agent` 并重写 `prepare_tools(tape, prompt)`。默认提供传入的工具,排除 `exposure="command"`。Agent 在每轮运行的第一个模型步骤前准备一次工具,之后每一步都提供同一组工具;调用不在其中的工具时,会重新执行 `prepare_tools(tape, None)` 来解析,因此运行中新变为可用的工具仍可调用。`tape.context.state` 就是本轮状态。 - -运行过程中到达的新消息和继续运行时的下一步提示,由两个循环事件控制: - -| 事件 | 发生时机 | 支持的操作 | -| --- | --- | --- | -| `MessagesPending` | 每个步骤构建模型请求时,以及步骤结束且没有调用工具时。 | 向 `event.messages` 追加内容;它们在下一个 prompt 之前发送,并一起记录。步骤结束时提供的消息会让运行继续。 | -| `StepFinished` | 步骤结束。`status == "continue"` 时运行会再走一步。 | 设置 `next_prompt` 为下一步添加用户消息;为 `None` 时直接继续对话。`event.prompt` 是本步骤的 prompt,`event.stream` 保存本次运行的用量。 | - -```python -from bub.core.events import MessagesPending, StepFinished - -inbox: dict[str, list[str]] = {} - - -@agent.events.on(MessagesPending) -def send_queued(event: MessagesPending) -> None: - event.messages.extend([text] for text in inbox.pop(event.tape.name, [])) - - -@agent.events.on(StepFinished) -def check_results(event: StepFinished) -> None: - if event.status == "continue": - event.next_prompt = ["Check the tool results before answering."] -``` - -如果要自己管理 Tape 生命周期,可以打开 `agent.session(name, state=...)`,再调用 `agent.run_on(tape, prompt, model=...)`。它返回的流需要由你关闭。`agent.run_once(prompt, model=..., tape=...)` 只产出一个模型步骤,适合自己驱动循环的场景。 - - - - -## 继续构建 +## 下一步 -- [工具与 CodeMode](/zh-cn/docs/sdk/tools/),让 Agent 执行动作。 -- [保存会话历史](/zh-cn/docs/concepts/tape/),在多轮运行之间继续工作。 -- [通过 FastAPI 提供服务](/zh-cn/docs/sdk/web-service/),让其他程序通过 HTTP 调用 Agent。 +- [配置 Agent](/zh-cn/docs/sdk/options/):模型、指令,以及 `Agent` 的其他参数。 +- [工具与 CodeMode](/zh-cn/docs/sdk/tools/):让 Agent 执行动作。 +- [复用插件](/zh-cn/docs/sdk/reuse-plugins/):把已有插件的工具和 hooks 搬进自己的 Agent。 +- [示例:FastAPI 服务](/zh-cn/docs/sdk/web-service/):把 Agent 放到 HTTP 端点后面。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/options.mdx b/website/src/content/docs/zh-cn/docs/sdk/options.mdx new file mode 100644 index 000000000..300489981 --- /dev/null +++ b/website/src/content/docs/zh-cn/docs/sdk/options.mdx @@ -0,0 +1,118 @@ +--- +title: 配置 Agent +description: 选择模型、指令,以及影响一次运行的其他选项。 +sidebar: + order: 1 +--- + +这些选项决定 Agent 怎么跑。先在[基础 Agent](/zh-cn/docs/sdk/)的示例上跑通,再按需修改。 + + + +## 选择模型 + +将 Republic chat model 传给 `Agent(model=...)`,可以设置默认模型;传给 `agent.run(model=...)`,则只用于当前运行。如果运行需要模型,而两处都没有提供,就会抛出 `ValueError`。模型的生命周期由你管理,请用 `async with` 包住使用它的运行过程。 + +Republic 可以直接接收凭据和服务地址: + +```python +async with republic.get_model( + "openai:gpt-5-mini", api_key="...", api_base="https://example.test/v1" +) as model: + agent = Agent(model=model) + async with agent.run("Hello", tape="example", options={"max_tokens": 4096}) as stream: + async for event in stream: + print(event.kind, event.data) +``` + +`options` 是每次模型调用使用的 Republic chat options,Agent 会补入当前步骤的工具。模型通过 Republic 的参数或环境变量配置;`BUB_*` 设置用于 Bub 应用。 + +如果需要自己的模型回退、凭据选择或超时策略,可以实现 `bub.core.ModelClient`: + +| 成员 | 要求 | +| --- | --- | +| `spec` | 默认的 `provider:model` 字符串。 | +| `max_tokens` | 默认输出 token 上限,或 `None`。 | +| `open_stream(messages, *, spec, options)` | 异步上下文管理器,产出 `(provider_name, republic_stream)`。messages 是 `republic.Message` 对象,options 包含本步骤的工具。 | + +`LLMCallStarted` 处理器可以更改请求的模型。使用 Republic chat model 时,同一供应商内的切换复用当前客户端;切换到其他供应商会读取 Republic 环境配置创建客户端。需要不同策略时,提供自己的 `ModelClient`。 + + + +## 设置指令 + +`system_prompt` 接收文本、函数,或按顺序排列的两者。函数接收 `PromptContext`,支持同步和异步。Agent 在每轮运行的第一个模型步骤前渲染一次这些片段,忽略空结果,再用空行拼接;同一轮运行的后续步骤复用这段文本。 + +```python +from bub.core import PromptContext + + +@agent.system_prompt.add +def customer(context: PromptContext) -> str | None: + if (name := context.state.get("user")) is not None: + return f"You are helping {name}." + return None +``` + +`PromptContext` 包含本轮运行的 prompt、提供给模型的工具、模型标识、Tape、本轮状态和 Agent。`available_tools` 列出 Agent 注册的工具。使用 `agent.system_prompt.remove(section)` 可以移除片段。 + + + + +传给运行的 `state` 是可变字典,由工具、提示词片段和事件处理器共用。哪些值需要保存、下次运行时如何恢复,由你决定,详见[本轮状态](/zh-cn/docs/concepts/tape/#state)。 + + + + + +## 设置其他参数 + +| 参数 | 默认值与用途 | +| --- | --- | +| `model` | `None`。默认 Republic chat model 或 `ModelClient`。 | +| `store` | 新建的 `InMemoryTapeStore`。也可以传入同步或异步存储。 | +| `tools` | 空。接收 `Tool` 对象,以及 `republic.tools.WebSearch()` 等供应商执行的工具。 | +| `system_prompt` | 空。每轮运行渲染一次的文本或函数片段。 | +| `context` | `default_tape_context()`。选择哪些 Tape 记录进入模型消息。 | +| `sidecars` | 空。与 Tape 一起挂载的附加存储能力。 | +| `max_steps` | `sys.maxsize`。一轮运行的模型步骤上限。 | +| `max_tool_concurrency` | `None`,同一次模型响应中的工具调用可以全部并发。设为 `1` 时依次执行。不同运行之间的并发由应用协调。 | +| `name` | `"agent"`。Agent trace span 使用的名称。 | +| `command_prefix` | `None`。设置前缀后,可以通过文本命令直接调用工具。 | + +传入的 `Tool` 按名称保存在 `agent.tools` 中,供应商工具保存在 `agent.builtin_tools` 中。构造或更新 Agent 时,由你显式选择这些工具。 + + + +## 定制步骤准备方式 + +当工具选择需要自己的业务逻辑时,可以继承 `Agent` 并重写 `prepare_tools(tape, prompt)`。默认提供传入的工具,排除 `exposure="command"`。Agent 在每轮运行的第一个模型步骤前准备一次工具,之后每一步都提供同一组工具;调用不在其中的工具时,会重新执行 `prepare_tools(tape, None)` 来解析,因此运行中新变为可用的工具仍可调用。`tape.context.state` 就是本轮状态。 + +运行过程中到达的新消息和继续运行时的下一步提示,由两个循环事件控制: + +| 事件 | 发生时机 | 支持的操作 | +| --- | --- | --- | +| `MessagesPending` | 每个步骤构建模型请求时,以及步骤结束且没有调用工具时。 | 向 `event.messages` 追加内容;它们在下一个 prompt 之前发送,并一起记录。步骤结束时提供的消息会让运行继续。 | +| `StepFinished` | 步骤结束。`status == "continue"` 时运行会再走一步。 | 设置 `next_prompt` 为下一步添加用户消息;为 `None` 时直接继续对话。`event.prompt` 是本步骤的 prompt,`event.stream` 保存本次运行的用量。 | + +```python +from bub.core.events import MessagesPending, StepFinished + +inbox: dict[str, list[str]] = {} + + +@agent.events.on(MessagesPending) +def send_queued(event: MessagesPending) -> None: + event.messages.extend([text] for text in inbox.pop(event.tape.name, [])) + + +@agent.events.on(StepFinished) +def check_results(event: StepFinished) -> None: + if event.status == "continue": + event.next_prompt = ["Check the tool results before answering."] +``` + +`agent.session(name, state=...)` 打开一个由你管理生命周期的 Tape。把它返回的 `Tape` 对象(不是名称)传给 `agent.run_on(tape, prompt, model=...)` 或 `agent.run_once(prompt, model=..., tape=)`。`run_on()` 返回的流需要由你关闭;`run_once()` 只产出一个模型步骤,适合自己驱动循环的场景。 + + + diff --git a/website/src/content/docs/zh-cn/docs/sdk/app-plugins.mdx b/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx similarity index 92% rename from website/src/content/docs/zh-cn/docs/sdk/app-plugins.mdx rename to website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx index 56ac55f27..32b87f0a7 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/app-plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx @@ -1,8 +1,8 @@ --- -title: 复用应用插件 +title: 复用插件 description: 将插件提供的工具和 hooks 接入自己的 Agent,保留现有服务入口与调用方式。 sidebar: - order: 1 + order: 4 --- 你可以在现有 Python 服务中使用 Bub 应用插件。宿主继续管理 HTTP 路由、任务入口和输出处理;根据要复用的应用行为,选择 Agent 和运行入口。 @@ -11,7 +11,7 @@ sidebar: 使用 `core.Agent` 时,由你选择组件并接入 hooks。下面分别取用搜索插件的工具、适配上下文插件的调用 hooks,每个示例都说明了该插件需要的依赖。后台服务还需要各自的启动和关闭流程。 -## 给 Agent 加入搜索插件 +## 加入搜索工具 [`bub-web-search`](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-web-search) 提供了可供 `core.Agent` 使用的搜索工具。将它安装到运行服务的 Python 环境中。本文示例使用 `bub-contrib` 的 `dev` 分支插件: @@ -70,7 +70,11 @@ if __name__ == "__main__": `register_tools()` 还会更新进程内的全局工具注册表,请在服务启动时完成这些准备。Agent 使用显式传入或加入的工具;本例取用返回的搜索工具,包注册的其他工具(如 Jina 的 `web.read`)可按需另行加入。 -## 进阶:接入单个插件的调用 hooks +## 扩展运行 + +`AgentPlugins` 把插件的 hooks 接到你的 Agent 事件上。 + +### 模型与工具调用 [`bub-context-budget`](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-context-budget) 在模型调用前后工作。当估算的上下文超过阈值时,它会加入一条指令,请模型调用 `tape.handoff`。将这个工具交给 Agent,再把插件 hooks 接到 Agent 事件上即可。 @@ -109,9 +113,9 @@ Handoff 把摘要保存在锚点上。下一次模型请求需要这份摘要时 -## 接入插件的其他 agent 循环 hooks +### 其他 hooks -`AgentPlugins` 接入所有影响 agent 循环的 hooks,不只是调用拦截。每个 hook 对应核心 Agent 的一个扩展点: +其余 hooks 决定一次运行中有什么。每个 hook 对应核心 Agent 的一个扩展点: | Hook | 核心 Agent 扩展点 | | --- | --- | @@ -137,9 +141,9 @@ async with plugins.running(): 没有 hook 提供的组件不会出现在 `components()` 中,Agent 保留默认值;你自己传入的参数会替代它们。hooks 提供的存储和 steering inbox 只在 `plugins.running()`(即 `framework.running()`)内可用。先调用 `attach()`,再注册自己的处理器,这样它们能看到 hooks 的修改。 -`build_prompt`、`run_model`、`save_state`、`render_outbound`、`dispatch_outbound` 等消息流程 hooks 不属于 agent 循环,只在 `process_inbound()` 中运行。`plugins=[...]` 只注册列出的插件。不带参数的 `AgentPlugins()` 和 `bub` 命令一样,加载 Bub 内置 hooks 和已安装的插件。传入 `framework` 则使用已有 framework 的 hooks,同时传入的 `plugins` 会注册到它上面。 +`build_prompt`、`run_model`、`save_state`、`render_outbound`、`dispatch_outbound` 等消息流程 hooks 不属于 agent 循环,只在 `process_inbound()` 中运行。`plugins=[...]` 只注册列出的插件。多个插件实现同一个 hook 时都会执行:重名的工具、存储和 Tape 上下文取优先级最高者,提示词片段按优先级从低到高拼接。不带参数的 `AgentPlugins()` 和 `bub` 命令一样,加载 Bub 内置 hooks 和已安装的插件。传入 `framework` 则使用已有 framework 的 hooks,同时传入的 `plugins` 会注册到它上面。 -## 按插件能力选择接法 +## 选择接入方式 宿主需要提供什么,取决于插件实现了哪些能力: @@ -156,9 +160,9 @@ async with plugins.running(): -## 复用 Bub 应用的默认行为 +## 复用 BubAgent -`BubAgent` 在内核循环上接入 Bub 设置、技能、工具策略和插件 hooks。宿主可以通过 `framework.load_hooks()` 加载已安装插件,进入 `framework.running()` 后构造 `BubAgent`。它的 `run_stream()` 返回由宿主消费和关闭的流。 +`BubAgent` 在内核循环上接入 Bub 设置、技能、工具策略和插件 hooks。宿主可以通过 `framework.load_hooks()` 加载已安装插件,进入 `framework.running()` 后构造 `BubAgent(framework)`。它的 `run_stream(*, session_id, prompt, state=None, ...)` 返回由宿主消费和关闭的流。 如果也需要这些应用默认行为,可以选择这种接法。内置系统提示词不要求 Agent 通过 channel skill 回复;`bub gateway` 和 `bub chat` 背后的 channel manager 会注册一个 `system_prompt` hook,只在它运行的轮次中加入这条指令。自己调用 `process_inbound()` 的服务可以注册自己的 `system_prompt` hook,说明回复如何送达。替换 `agent.system_prompt` 会替换所有片段,包括插件、技能、延迟工具和 CodeMode 的指令。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx index 34fa4f770..54df4a190 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx @@ -31,11 +31,11 @@ agent = Agent(tools=[add_tool]) 运行时保留 `math.add` 这个名称;提供给模型时,点会换成下划线,因此模型看到的是 `math_add`。也可以通过 `description=` 显式提供描述。 -普通 `Agent` 会把传入的工具提供给模型,仅排除 `exposure="command"`。延迟加载、`allowed_tools` 状态和 `state["code_mode"]` 开关属于 [BubAgent 的应用策略](/zh-cn/docs/app/tools/)。在自己的 Agent 中按轮次选择工具,可以覆写 [`prepare_tools()`](/zh-cn/docs/sdk/#customize-the-loop)。 +普通 `Agent` 会把传入的工具提供给模型,仅排除 `exposure="command"`。延迟加载、`allowed_tools` 状态和 `state["code_mode"]` 开关属于 [BubAgent 的应用策略](/zh-cn/docs/app/tools/)。在自己的 Agent 中按轮次选择工具,可以覆写 [`prepare_tools()`](/zh-cn/docs/sdk/options/#customize-the-loop)。 -## 返回调用方需要的数据 +## 返回可用数据 工具可以返回字典、列表、带类型的值或字符串。模型直接调用工具时,可以用 `renderer` 将结构化结果转成文本: @@ -103,7 +103,7 @@ agent = Agent( ) ``` -按 [SDK 指南](/zh-cn/docs/sdk/#models)中的方式管理模型并运行 Agent。模型可以向 `run_code` 传入这样的代码: +按 [SDK 指南](/zh-cn/docs/sdk/options/#models)中的方式管理模型并运行 Agent。模型可以向 `run_code` 传入这样的代码: ```python order = await tools.lookup_order(order_id="A123") @@ -121,6 +121,7 @@ print(order["status"]) | `models` | 代码调用 Republic 时使用的默认 `provider:model`,或返回模型标识与客户端选项的解析函数。不设置时,代码需要给出模型标识,Republic 从环境读取凭据。 | | `stub_dir` | 保存生成的 Python 工具定义的目录。默认直接把定义放入系统提示词;写入文件时,Agent 需要有读取该文件的能力。 | | `media_tools` | 默认 `True`。增加用于返回媒体的 `attach_image`、`attach_audio`、`attach_video`。 | +| `name` | `run_code` 工具的名称,默认 `run_code`。 | 代码可以通过 Republic 桥接接口调用聊天、嵌入和决策模型。请求经过宿主进程的模型解析器,由宿主处理这些调用的供应商凭据。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/web-service.mdx b/website/src/content/docs/zh-cn/docs/sdk/web-service.mdx index 8dbd1fceb..f05a35b3d 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/web-service.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/web-service.mdx @@ -1,8 +1,8 @@ --- -title: 通过 FastAPI 提供 Agent 服务 +title: 示例:FastAPI 服务 description: 为 Agent 提供 HTTP 接口,并为每个会话保留独立历史。 sidebar: - order: 7 + order: 5 --- 为 Agent 提供 HTTP 接口,让其他程序可以向它提交任务。每个请求指定会话名称;使用相同名称的请求会继续磁盘上保存的会话。 @@ -19,9 +19,7 @@ cd bub-api uv add bub fastapi uvicorn ``` -这个服务使用基础 SDK 依赖,以及 FastAPI 和 Uvicorn。 - -如果要接入已有的 Bub 插件,见[复用应用插件](/zh-cn/docs/sdk/app-plugins/)。加入插件工具或指定 hooks 后,仍可沿用下面的接口与请求生命周期。 +如果要接入已有的 Bub 插件,见[复用插件](/zh-cn/docs/sdk/reuse-plugins/)。加入插件工具或指定 hooks 后,仍可沿用下面的接口与请求生命周期。 ## 编写服务 From 6e456ec1f02236d52e975b41a7b817bad864213d Mon Sep 17 00:00:00 2001 From: 100gle Date: Wed, 21 Oct 2026 17:08:39 +0800 Subject: [PATCH 06/28] docs(integrations): name each page after what it connects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The section labeled every page with a surface badge, including the three pages that work from either surface ("Both"), so the badge said nothing. Page titles also described the reader's situation ("Run Bub with a local llama.cpp model") instead of the thing being connected. - drop the generic badges; keep App only on MCP, Agent Plugins, and ACP, which need the application: a plugin install, the startup loader, or `bub acp` - name the pages after the connection: Connect an IDE over ACP, Use a local llama.cpp model, Store conversation history in SQLite, Observability - restore the overview page and call it Overview / 概览, so the group has a landing page again; its link text follows the new titles - order the group from tools to packaging to editor to model to storage to tracing - drop the sidebar order fields the config already sets by position --- website/astro.config.mjs | 6 +++--- .../src/content/docs/docs/integrations/acp.mdx | 3 +-- .../docs/docs/integrations/agent-plugins.mdx | 1 - .../src/content/docs/docs/integrations/index.mdx | 10 ++++------ .../docs/docs/integrations/local-models.mdx | 5 +---- .../src/content/docs/docs/integrations/mcp.mdx | 1 - .../docs/docs/integrations/observability.mdx | 5 +---- .../content/docs/docs/integrations/storage.mdx | 5 +---- .../content/docs/zh-cn/docs/integrations/acp.mdx | 3 +-- .../zh-cn/docs/integrations/agent-plugins.mdx | 3 +-- .../docs/zh-cn/docs/integrations/index.mdx | 16 +++++++--------- .../zh-cn/docs/integrations/local-models.mdx | 5 +---- .../content/docs/zh-cn/docs/integrations/mcp.mdx | 3 +-- .../zh-cn/docs/integrations/observability.mdx | 5 +---- .../docs/zh-cn/docs/integrations/storage.mdx | 5 +---- 15 files changed, 24 insertions(+), 52 deletions(-) diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 0ad37d296..daad65f01 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -271,12 +271,12 @@ export default defineConfig({ translations: { 'zh-CN': '集成' }, items: [ 'docs/integrations', - 'docs/integrations/local-models', - 'docs/integrations/storage', - 'docs/integrations/observability', 'docs/integrations/mcp', 'docs/integrations/agent-plugins', 'docs/integrations/acp', + 'docs/integrations/local-models', + 'docs/integrations/storage', + 'docs/integrations/observability', ], }, { diff --git a/website/src/content/docs/docs/integrations/acp.mdx b/website/src/content/docs/docs/integrations/acp.mdx index 4cbf27d20..ff9b00324 100644 --- a/website/src/content/docs/docs/integrations/acp.mdx +++ b/website/src/content/docs/docs/integrations/acp.mdx @@ -1,9 +1,8 @@ --- -title: Connect an ACP editor +title: Connect an IDE over ACP description: Install bub-acp-server and connect Bub to an ACP-compatible editor such as Zed. sidebar: badge: { text: 'App', variant: 'note' } - order: 6 --- Use the Bub application from an editor that supports the [Agent Client Protocol](https://agentclientprotocol.com/). The `bub-acp-server` plugin connects the editor to the app's existing model configuration, tools, and conversation history. diff --git a/website/src/content/docs/docs/integrations/agent-plugins.mdx b/website/src/content/docs/docs/integrations/agent-plugins.mdx index c5ecb9453..de17de947 100644 --- a/website/src/content/docs/docs/integrations/agent-plugins.mdx +++ b/website/src/content/docs/docs/integrations/agent-plugins.mdx @@ -3,7 +3,6 @@ title: Load Agent Plugins description: Load a portable Agent Plugin with a skill and an MCP server, and use it alongside existing Bub integrations. sidebar: badge: { text: 'App', variant: 'note' } - order: 4 --- `bub-agent-plugins` adds support for Agent Plugins 1.0.0 packages: directories with a `plugin.json` manifest diff --git a/website/src/content/docs/docs/integrations/index.mdx b/website/src/content/docs/docs/integrations/index.mdx index e4055f4b1..6cd77783f 100644 --- a/website/src/content/docs/docs/integrations/index.mdx +++ b/website/src/content/docs/docs/integrations/index.mdx @@ -1,8 +1,6 @@ --- -title: Connect Bub to your tools -description: Add an HTTP service, an editor, a model server, or an application integration. -sidebar: - order: 0 +title: Overview +description: "Choose an integration for how you use Bub: MCP tools, an editor, a local model, storage, or tracing." --- Choose an integration for the way you use Bub. Each guide names the components it needs and gives you a way to check the result. @@ -17,10 +15,10 @@ These guides use the app's CLI, configuration, or plugin system. Start with the - [Connect MCP servers](/docs/integrations/mcp/): call tools supplied by an MCP server. - [Load Agent Plugins](/docs/integrations/agent-plugins/): load a portable package of skills and MCP servers. -- [Connect an ACP editor](/docs/integrations/acp/): use Bub from an editor such as Zed. +- [Connect an IDE over ACP](/docs/integrations/acp/): use Bub from an editor such as Zed. - [Use a local llama.cpp model](/docs/integrations/local-models/): point the app at a local model server. - [Store conversation history in SQLite](/docs/integrations/storage/): replace the app's file store through a plugin. -- [Inspect runs with tapes and traces](/docs/integrations/observability/): inspect local records or export traces to Phoenix and Logfire. +- [Observability](/docs/integrations/observability/): inspect local records or export traces to Phoenix and Logfire. - [WASM plugins](/docs/app/plugins/#wasm-plugins): write a hook in Go or Rust and run it through WebAssembly. Each integration guide links to the plugin's documentation for package-specific setup. diff --git a/website/src/content/docs/docs/integrations/local-models.mdx b/website/src/content/docs/docs/integrations/local-models.mdx index 30f5c3fcf..19784f50d 100644 --- a/website/src/content/docs/docs/integrations/local-models.mdx +++ b/website/src/content/docs/docs/integrations/local-models.mdx @@ -1,9 +1,6 @@ --- -title: Run Bub with a local llama.cpp model +title: Use a local llama.cpp model description: Start a local llama.cpp server and configure Bub to use it as an OpenAI-compatible model provider. -sidebar: - badge: { text: 'Both', variant: 'success' } - order: 3 --- Connect the Bub application to a local `llama.cpp` server. This guide starts a GGUF Gemma model, checks its OpenAI-compatible endpoint, and sets the app's model connection. diff --git a/website/src/content/docs/docs/integrations/mcp.mdx b/website/src/content/docs/docs/integrations/mcp.mdx index 9dc0e20df..b66140796 100644 --- a/website/src/content/docs/docs/integrations/mcp.mdx +++ b/website/src/content/docs/docs/integrations/mcp.mdx @@ -3,7 +3,6 @@ title: Connect MCP servers description: Install the bub-mcp plugin, register a time MCP server, and call its tools from a Bub turn. sidebar: badge: { text: 'App', variant: 'note' } - order: 2 --- Connect an MCP server to the Bub application with the `bub-mcp` plugin. This guide registers a time server, checks the connection, and calls one of its tools. diff --git a/website/src/content/docs/docs/integrations/observability.mdx b/website/src/content/docs/docs/integrations/observability.mdx index c084bc133..23cd71a5b 100644 --- a/website/src/content/docs/docs/integrations/observability.mdx +++ b/website/src/content/docs/docs/integrations/observability.mdx @@ -1,9 +1,6 @@ --- -title: Inspect app runs +title: Observability description: Inspect conversation records and export model and tool traces to Phoenix or Logfire. -sidebar: - badge: { text: 'Both', variant: 'success' } - order: 1 --- Inspect a Bub application's conversation history, then export execution traces when you need model and tool timing. Start with the local tape commands; add Phoenix or Logfire to inspect a run across calls. diff --git a/website/src/content/docs/docs/integrations/storage.mdx b/website/src/content/docs/docs/integrations/storage.mdx index da11a600b..fbe0be69f 100644 --- a/website/src/content/docs/docs/integrations/storage.mdx +++ b/website/src/content/docs/docs/integrations/storage.mdx @@ -1,9 +1,6 @@ --- -title: Store app history in SQLite +title: Store conversation history in SQLite description: Replace the Bub application's file store with SQLite through a tape store plugin. -sidebar: - badge: { text: 'Both', variant: 'success' } - order: 2 --- Store the Bub application's conversation history in a local SQLite database with [`bub-tapestore-sqlalchemy`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-tapestore-sqlalchemy). The plugin replaces the app's default file store; you can continue using `,tape.info` and `,tape.search` to inspect records. diff --git a/website/src/content/docs/zh-cn/docs/integrations/acp.mdx b/website/src/content/docs/zh-cn/docs/integrations/acp.mdx index 9bf2a14fd..e2ba83764 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/acp.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/acp.mdx @@ -1,9 +1,8 @@ --- -title: 接入 ACP 编辑器 +title: 通过 ACP 接入 IDE description: 安装 bub-acp-server,并把 Bub 接入 Zed 等支持 ACP 的编辑器。 sidebar: badge: { text: '应用', variant: 'note' } - order: 6 --- 在支持 [Agent Client Protocol](https://agentclientprotocol.com/) 的编辑器中使用 Bub 应用。`bub-acp-server` 插件让编辑器接入应用已有的模型配置、工具和会话历史。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/agent-plugins.mdx b/website/src/content/docs/zh-cn/docs/integrations/agent-plugins.mdx index 2d1e5b6a3..dc63f2806 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/agent-plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/agent-plugins.mdx @@ -3,7 +3,6 @@ title: 加载 Agent Plugins description: 加载包含技能和 MCP 服务器的可移植 Agent Plugin,与现有 Bub 扩展一起使用。 sidebar: badge: { text: '应用', variant: 'note' } - order: 4 --- `bub-agent-plugins` 支持 Agent Plugins 1.0.0 格式的插件包:一个包含 `plugin.json` 清单,以及可选 @@ -205,6 +204,6 @@ stdio 服务器会收到绝对路径形式的 `PLUGIN_ROOT` 和 `PLUGIN_DATA` ## 下一步 -- [连接 MCP 服务器](/zh-cn/docs/integrations/mcp/):管理 Bub 数据目录配置中的服务器。 +- [接入 MCP 服务器](/zh-cn/docs/integrations/mcp/):管理 Bub 数据目录配置中的服务器。 - [构建技能](/zh-cn/docs/app/skills/):编写并验证 Bub 技能。 - [构建插件](/zh-cn/docs/app/plugins/):使用 Python 包扩展 Bub hooks。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/index.mdx b/website/src/content/docs/zh-cn/docs/integrations/index.mdx index cd0593efb..81ca5cf96 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/index.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/index.mdx @@ -1,8 +1,6 @@ --- -title: 把 Bub 接入你的工具 -description: 接入 HTTP 服务、编辑器、模型服务或应用扩展。 -sidebar: - order: 0 +title: 概览 +description: 按使用 Bub 的方式选择集成:MCP 工具、编辑器、本地模型、存储或可观测性。 --- 按你使用 Bub 的方式选择接入指南。每篇指南会说明所需组件,并给出检查结果的方法。 @@ -15,12 +13,12 @@ sidebar: 这些指南使用应用的 CLI、配置或插件机制。先[安装 Bub 应用](/zh-cn/docs/getting-started/install/),把 Bub 和插件放在同一个环境中。 -- [连接 MCP 服务器](/zh-cn/docs/integrations/mcp/):调用 MCP 服务器提供的工具。 +- [接入 MCP 服务器](/zh-cn/docs/integrations/mcp/):调用 MCP 服务器提供的工具。 - [加载 Agent Plugins](/zh-cn/docs/integrations/agent-plugins/):加载包含技能和 MCP 服务器的可移植插件包。 -- [接入 ACP 编辑器](/zh-cn/docs/integrations/acp/):在 Zed 等编辑器中使用 Bub。 -- [使用本地 llama.cpp 模型](/zh-cn/docs/integrations/local-models/):让应用调用本地模型服务。 -- [把会话历史存入 SQLite](/zh-cn/docs/integrations/storage/):通过插件替换应用的文件存储。 -- [检查会话记录和执行轨迹](/zh-cn/docs/integrations/observability/):查看本地记录,或将轨迹导出到 Phoenix 和 Logfire。 +- [通过 ACP 接入 IDE](/zh-cn/docs/integrations/acp/):在 Zed 等编辑器中使用 Bub。 +- [接入本地 llama.cpp 模型](/zh-cn/docs/integrations/local-models/):让应用调用本地模型服务。 +- [用 SQLite 存储会话历史](/zh-cn/docs/integrations/storage/):通过插件替换应用的文件存储。 +- [可观测性](/zh-cn/docs/integrations/observability/):查看本地记录,或将轨迹导出到 Phoenix 和 Logfire。 - [WASM 插件](/zh-cn/docs/app/plugins/#wasm-plugins):用 Go 或 Rust 写 hook,通过 WebAssembly 运行。 各集成指南都提供插件文档链接,可据此查看该插件的具体配置方式。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx b/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx index 02cb690bb..152c3921d 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx @@ -1,9 +1,6 @@ --- -title: 使用本地 llama.cpp 模型运行 Bub +title: 接入本地 llama.cpp 模型 description: 启动本地 llama.cpp 服务,并把 Bub 配置为使用这个 OpenAI 兼容模型后端。 -sidebar: - badge: { text: '通用', variant: 'success' } - order: 3 --- 让 Bub 应用调用本机的 `llama.cpp` 服务。本指南启动一个 GGUF Gemma 模型,检查其 OpenAI 兼容端点,再设置应用的模型地址。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx index 05ca53221..231864245 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx @@ -1,9 +1,8 @@ --- -title: 连接 MCP 服务器 +title: 接入 MCP 服务器 description: 安装 bub-mcp 插件,注册一个时间 MCP 服务器,并在 Bub 轮次中调用其工具。 sidebar: badge: { text: '应用', variant: 'note' } - order: 2 --- 通过 `bub-mcp` 插件,把 MCP 服务器接入 Bub 应用。本指南注册一个时间服务器,验证连接,再调用它提供的工具。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/observability.mdx b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx index 3ebae975e..93e37230e 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/observability.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx @@ -1,9 +1,6 @@ --- -title: 检查应用运行 +title: 可观测性 description: 检查会话记录,并把模型与工具调用轨迹导出到 Phoenix 或 Logfire。 -sidebar: - badge: { text: '通用', variant: 'success' } - order: 1 --- 先查看 Bub 应用的会话历史,再按需导出执行轨迹,检查模型与工具调用的耗时。你可以从本地 Tape 命令开始,接入 Phoenix 或 Logfire 后查看一次运行中的多次调用。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/storage.mdx b/website/src/content/docs/zh-cn/docs/integrations/storage.mdx index bf1929531..92c0f65f6 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/storage.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/storage.mdx @@ -1,9 +1,6 @@ --- -title: 把应用会话历史存入 SQLite +title: 用 SQLite 存储会话历史 description: 通过存储插件,把 Bub 应用的会话历史从文件改存到 SQLite。 -sidebar: - badge: { text: '通用', variant: 'success' } - order: 2 --- 通过 [`bub-tapestore-sqlalchemy`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-tapestore-sqlalchemy),把 Bub 应用的会话历史存入本地 SQLite 数据库。插件替换应用默认的文件存储,你仍可用 `,tape.info` 和 `,tape.search` 检查记录。 From a1016ef5e9c12840ee4b1c4f9252e0eda5467697 Mon Sep 17 00:00:00 2001 From: 100gle Date: Wed, 21 Oct 2026 17:34:56 +0800 Subject: [PATCH 07/28] docs(reference): merge the interfaces into one Python API page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reference opened with an index whose top half pointed at /docs/sdk and /docs/concepts, and it listed a page called "Application types and lifecycle" that also documented streams and errors from the core loop. The 0.6 upgrade note sat among the lookup pages although it describes a one-off dependency change. - drop the reference index; the group starts at CLI and /docs/reference redirects there - merge the core and application interfaces into Python API, split into Core and Application sections - move the 0.6 upgrade note to its own Migration group as "Upgrade to 0.6.0", so the sidebar does not show a bare version number - drop the Application prefix from CLI, Settings, and Hooks - call the settings page 配置项 so it shares the 配置 root with the configuration guide instead of adding 设置 as a second term - keep /docs/reference/types and /docs/reference/versions redirecting to their new pages --- website/astro.config.mjs | 15 ++- website/src/content/docs/docs/app/deploy.mdx | 2 +- .../content/docs/docs/app/distribution.mdx | 2 +- website/src/content/docs/docs/app/hooks.mdx | 2 +- .../src/content/docs/docs/app/terminal.mdx | 2 +- .../src/content/docs/docs/concepts/events.mdx | 2 +- .../src/content/docs/docs/concepts/turn.mdx | 2 +- .../docs/docs/getting-started/index.mdx | 2 +- .../docs/docs/getting-started/install.mdx | 2 +- .../content/docs/docs/integrations/mcp.mdx | 2 +- .../versions.mdx => migration/0-6.mdx} | 0 .../src/content/docs/docs/reference/cli.mdx | 2 +- .../src/content/docs/docs/reference/hooks.mdx | 2 +- .../src/content/docs/docs/reference/index.mdx | 32 ------- .../reference/{types.mdx => python-api.mdx} | 92 ++++++++++--------- .../content/docs/docs/reference/settings.mdx | 2 +- .../content/docs/zh-cn/docs/app/configure.mdx | 6 +- .../content/docs/zh-cn/docs/app/deploy.mdx | 2 +- .../docs/zh-cn/docs/app/distribution.mdx | 2 +- .../src/content/docs/zh-cn/docs/app/hooks.mdx | 2 +- .../content/docs/zh-cn/docs/app/terminal.mdx | 2 +- .../docs/zh-cn/docs/concepts/events.mdx | 2 +- .../content/docs/zh-cn/docs/concepts/turn.mdx | 4 +- .../docs/zh-cn/docs/getting-started/index.mdx | 2 +- .../zh-cn/docs/getting-started/install.mdx | 2 +- .../docs/zh-cn/docs/integrations/mcp.mdx | 2 +- .../versions.mdx => migration/0-6.mdx} | 0 .../content/docs/zh-cn/docs/reference/cli.mdx | 2 +- .../docs/zh-cn/docs/reference/hooks.mdx | 2 +- .../docs/zh-cn/docs/reference/index.mdx | 32 ------- .../reference/{types.mdx => python-api.mdx} | 92 ++++++++++--------- .../docs/zh-cn/docs/reference/settings.mdx | 4 +- 32 files changed, 141 insertions(+), 180 deletions(-) rename website/src/content/docs/docs/{reference/versions.mdx => migration/0-6.mdx} (100%) delete mode 100644 website/src/content/docs/docs/reference/index.mdx rename website/src/content/docs/docs/reference/{types.mdx => python-api.mdx} (87%) rename website/src/content/docs/zh-cn/docs/{reference/versions.mdx => migration/0-6.mdx} (100%) delete mode 100644 website/src/content/docs/zh-cn/docs/reference/index.mdx rename website/src/content/docs/zh-cn/docs/reference/{types.mdx => python-api.mdx} (88%) diff --git a/website/astro.config.mjs b/website/astro.config.mjs index daad65f01..da2f55700 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -111,6 +111,12 @@ export default defineConfig({ '/zh-cn/docs/tutorials/mcp': '/zh-cn/docs/integrations/mcp/', '/zh-cn/docs/tutorials/observability': '/zh-cn/docs/integrations/observability/', '/zh-cn/docs/tutorials/tapestore-sqlalchemy': '/zh-cn/docs/integrations/storage/', + '/docs/reference/types': '/docs/reference/python-api/', + '/docs/reference': '/docs/reference/cli/', + '/docs/reference/versions': '/docs/migration/0-6/', + '/zh-cn/docs/reference/types': '/zh-cn/docs/reference/python-api/', + '/zh-cn/docs/reference': '/zh-cn/docs/reference/cli/', + '/zh-cn/docs/reference/versions': '/zh-cn/docs/migration/0-6/', }, env: { schema: { @@ -283,14 +289,17 @@ export default defineConfig({ label: 'Reference', translations: { 'zh-CN': '参考' }, items: [ - 'docs/reference', 'docs/reference/cli', + 'docs/reference/python-api', 'docs/reference/settings', - 'docs/reference/types', 'docs/reference/hooks', - 'docs/reference/versions', ], }, + { + label: 'Migration', + translations: { 'zh-CN': '迁移' }, + items: ['docs/migration/0-6'], + }, ], }), ], diff --git a/website/src/content/docs/docs/app/deploy.mdx b/website/src/content/docs/docs/app/deploy.mdx index 0d762b0c5..7faaffec6 100644 --- a/website/src/content/docs/docs/app/deploy.mdx +++ b/website/src/content/docs/docs/app/deploy.mdx @@ -141,4 +141,4 @@ Both commands operate on the managed project at `BUB_PROJECT` (default `bub.home - [Configure](/docs/app/configure/): env-var precedence and provider settings. - [Channels](/docs/app/channels/#telegram): bot token and allowlist behavior. -- [CLI reference](/docs/reference/cli/): flags for `bub gateway`, `bub install`, `bub update`. +- [CLI](/docs/reference/cli/): flags for `bub gateway`, `bub install`, `bub update`. diff --git a/website/src/content/docs/docs/app/distribution.mdx b/website/src/content/docs/docs/app/distribution.mdx index 38866007f..87893ff83 100644 --- a/website/src/content/docs/docs/app/distribution.mdx +++ b/website/src/content/docs/docs/app/distribution.mdx @@ -72,7 +72,7 @@ my-bub = "my_bub.cli:main" Users can then run `my-bub chat` or `my-bub gateway`. Set a product-specific working directory before calling Bub if your application requires one; the framework takes its initial workspace from the current directory. -Declare `bub[app]>=0.6.0` as the application dependency. When upgrading an existing distribution, follow [Upgrade to 0.6.0](/docs/reference/versions/). +Declare `bub[app]>=0.6.0` as the application dependency. When upgrading an existing distribution, follow [Upgrade to 0.6.0](/docs/migration/0-6/). ## Bundle plugins and skills diff --git a/website/src/content/docs/docs/app/hooks.mdx b/website/src/content/docs/docs/app/hooks.mdx index d0c86fa4c..80a61e767 100644 --- a/website/src/content/docs/docs/app/hooks.mdx +++ b/website/src/content/docs/docs/app/hooks.mdx @@ -92,7 +92,7 @@ def after_tool_call(call, result, state): print(f"tool={call.tool} duration_ms={result.duration_ms} error={result.error!r}") ``` -These hooks observe completed calls, including ordinary failures. They do not report cancellation. For per-chunk output, use the stream interface in your host or channel and follow its [closing rules](/docs/reference/types/#streaming). +These hooks observe completed calls, including ordinary failures. They do not report cancellation. For per-chunk output, use the stream interface in your host or channel and follow its [closing rules](/docs/reference/python-api/#streaming). To replace the entire model stage, implement either `run_model` or `run_model_stream`. [Your first plugin](/docs/app/plugins/) shows a text replacement. A replacement owns its execution and recording behavior; the builtin agent's interception hooks run only when that agent is used. diff --git a/website/src/content/docs/docs/app/terminal.mdx b/website/src/content/docs/docs/app/terminal.mdx index 0c5139d28..1a021c705 100644 --- a/website/src/content/docs/docs/app/terminal.mdx +++ b/website/src/content/docs/docs/app/terminal.mdx @@ -43,4 +43,4 @@ bub run ",fs.read path=README.md" You can enter these commands in an interactive conversation too. Ctrl-X toggles shell mode, which adds the command prefix to your input. -The CLI owns the foreground terminal, so the gateway excludes it by default. See [Channels](/docs/app/channels/) for gateway selection and batching, or the [CLI reference](/docs/reference/cli/) for every option. +The CLI owns the foreground terminal, so the gateway excludes it by default. See [Channels](/docs/app/channels/) for gateway selection and batching, or the [CLI](/docs/reference/cli/) for every option. diff --git a/website/src/content/docs/docs/concepts/events.mdx b/website/src/content/docs/docs/concepts/events.mdx index 860a90f6d..710e4ee0c 100644 --- a/website/src/content/docs/docs/concepts/events.mdx +++ b/website/src/content/docs/docs/concepts/events.mdx @@ -132,6 +132,6 @@ async def mark_reviewed() -> None: This adapter follows the plugin hook order. It catches exceptions from individual interception hooks, logs them, and skips them. To deny a call, return the correct decision. The other app hooks follow their own error contracts. -See [Hooks](/docs/app/hooks/) for plugin examples. For order and signatures, see [Application hooks](/docs/reference/hooks/). EventBus is part of the base SDK installation. +See [Hooks](/docs/app/hooks/) for plugin examples. For order and signatures, see [Hooks reference](/docs/reference/hooks/). EventBus is part of the base SDK installation. `framework.events` publishes commands, channel work, and user prompts. It uses `CommandChanged`, `WorkChanged`, and `PromptChanged` from `bub.application`. You subscribe and unsubscribe with the same `on()` and `off()` API. diff --git a/website/src/content/docs/docs/concepts/turn.mdx b/website/src/content/docs/docs/concepts/turn.mdx index ad290626b..b6bacb488 100644 --- a/website/src/content/docs/docs/concepts/turn.mdx +++ b/website/src/content/docs/docs/concepts/turn.mdx @@ -45,4 +45,4 @@ If the model stage raises an unhandled exception, the framework runs `save_state ## Next steps -Use [plugin setup](/docs/app/plugins/) to register your implementation. For common changes, see [Hooks](/docs/app/hooks/). For exact signatures and ordering, see [Application hooks](/docs/reference/hooks/). +Use [plugin setup](/docs/app/plugins/) to register your implementation. For common changes, see [Hooks](/docs/app/hooks/). For exact signatures and ordering, see [Hooks reference](/docs/reference/hooks/). diff --git a/website/src/content/docs/docs/getting-started/index.mdx b/website/src/content/docs/docs/getting-started/index.mdx index 60959a19a..1a0e59d35 100644 --- a/website/src/content/docs/docs/getting-started/index.mdx +++ b/website/src/content/docs/docs/getting-started/index.mdx @@ -65,4 +65,4 @@ The rest of the documentation, in reading order: - **[Use the Bub app](/docs/app/workspace/)**: configure it, then add tools, skills, plugins, hooks, and channels. - **[Build your own agent](/docs/sdk/)**: keep the kernel and write the loop's surroundings yourself. - **[Integrations](/docs/integrations/)**: MCP, ACP, agent plugins, local models, SQLite, and tracing. -- **[Reference](/docs/reference/)**: CLI commands, settings, types, hooks, and version history. +- **[Reference](/docs/reference/cli/)**: CLI commands, settings, Python API, and hooks. diff --git a/website/src/content/docs/docs/getting-started/install.mdx b/website/src/content/docs/docs/getting-started/install.mdx index 44bf027ad..f5233299a 100644 --- a/website/src/content/docs/docs/getting-started/install.mdx +++ b/website/src/content/docs/docs/getting-started/install.mdx @@ -101,7 +101,7 @@ For a package that imports the builtin agent, Framework, plugins, skills, or cha dependencies = ["bub[app]>=0.6.0"] ``` -Synchronize your project after editing its dependencies. Existing applications should follow [Upgrade to 0.6.0](/docs/reference/versions/) when updating the dependency. +Synchronize your project after editing its dependencies. Existing applications should follow [Upgrade to 0.6.0](/docs/migration/0-6/) when updating the dependency. diff --git a/website/src/content/docs/docs/integrations/mcp.mdx b/website/src/content/docs/docs/integrations/mcp.mdx index b66140796..d07b184f6 100644 --- a/website/src/content/docs/docs/integrations/mcp.mdx +++ b/website/src/content/docs/docs/integrations/mcp.mdx @@ -25,7 +25,7 @@ You need: bub install bub-mcp@main ``` -`bub install` uses the active Bub environment. See the [CLI reference](/docs/reference/cli/#bub-install) for plugin source and dependency handling. +`bub install` uses the active Bub environment. See the [CLI](/docs/reference/cli/#bub-install) for plugin source and dependency handling. [Agent Plugins](/docs/integrations/agent-plugins/) can also load skills and their own MCP servers alongside these servers. That integration uses a separate lifecycle channel and plugin-qualified tool names. diff --git a/website/src/content/docs/docs/reference/versions.mdx b/website/src/content/docs/docs/migration/0-6.mdx similarity index 100% rename from website/src/content/docs/docs/reference/versions.mdx rename to website/src/content/docs/docs/migration/0-6.mdx diff --git a/website/src/content/docs/docs/reference/cli.mdx b/website/src/content/docs/docs/reference/cli.mdx index 41ee9e020..a69a2c603 100644 --- a/website/src/content/docs/docs/reference/cli.mdx +++ b/website/src/content/docs/docs/reference/cli.mdx @@ -1,5 +1,5 @@ --- -title: CLI reference +title: CLI description: Built-in application commands, options, and defaults. --- diff --git a/website/src/content/docs/docs/reference/hooks.mdx b/website/src/content/docs/docs/reference/hooks.mdx index d2b23b50c..0f4742180 100644 --- a/website/src/content/docs/docs/reference/hooks.mdx +++ b/website/src/content/docs/docs/reference/hooks.mdx @@ -1,5 +1,5 @@ --- -title: Application hooks +title: Hooks description: Hook signatures, dispatch rules, and resource lifecycles for Bub plugins. --- diff --git a/website/src/content/docs/docs/reference/index.mdx b/website/src/content/docs/docs/reference/index.mdx deleted file mode 100644 index a1e7e46ec..000000000 --- a/website/src/content/docs/docs/reference/index.mdx +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Reference -description: Find the Agent interfaces, shared capability contracts, and Bub application APIs. ---- - -Look up the interface used by your code. The Agent interfaces and application extension points have different responsibilities; [shared concepts](/docs/concepts/) explain how they fit together. - - - -## Agent interfaces - -| Interface | Reference | -| --- | --- | -| Agent construction and model clients | [Agent parameters](/docs/sdk/options/#agent-parameters) and [models](/docs/sdk/options/#models) | -| Runs and stream ownership | [Streams and lifecycle](/docs/sdk/#streams-and-lifecycle) | -| Prompt sections and loop customization | [System prompt](/docs/sdk/options/#system-prompt) and [customize the loop](/docs/sdk/options/#customize-the-loop) | -| Tools and CodeMode | [Tools](/docs/sdk/tools/) | -| Tape, storage, and context | [Tape and context](/docs/concepts/tape/) | -| Event delivery and decisions | [Agent events](/docs/concepts/events/) | - - - -## Bub application - -| Interface | Reference | -| --- | --- | -| Plugin hook names, signatures, and return values | [Hooks](/docs/reference/hooks/) | -| CLI commands and options | [CLI](/docs/reference/cli/) | -| Environment variables and configuration keys | [Settings](/docs/reference/settings/) | -| Framework, channels, envelopes, and application types | [Application contracts](/docs/reference/types/) | - -[Upgrade to 0.6.0](/docs/reference/versions/) explains the dependency change for existing applications. diff --git a/website/src/content/docs/docs/reference/types.mdx b/website/src/content/docs/docs/reference/python-api.mdx similarity index 87% rename from website/src/content/docs/docs/reference/types.mdx rename to website/src/content/docs/docs/reference/python-api.mdx index a0ea3ae3c..9026b4112 100644 --- a/website/src/content/docs/docs/reference/types.mdx +++ b/website/src/content/docs/docs/reference/python-api.mdx @@ -1,48 +1,17 @@ --- -title: Application types and lifecycle -description: Message, turn, stream, and channel interfaces used by the Bub application. +title: Python API +description: Core agent and Bub application interfaces, grouped by what they belong to. --- -Use these types when writing a plugin, adding a channel, or embedding the Bub application. Shared agent capabilities are covered in [Tools](/docs/sdk/tools/), [Tape and context](/docs/concepts/tape/), and [Events](/docs/concepts/events/). +Look up an interface by what it belongs to. Core interfaces work in a Python program that supplies a model and tools. Application interfaces apply when you run the Bub application, write a plugin, or add a channel. The guides cover [Tools](/docs/sdk/tools/), [Tape and context](/docs/concepts/tape/), and [Events](/docs/concepts/events/). -## Messages and turn results +## Core -Import message helpers from `bub.envelope`: - -```python -from bub.envelope import Envelope, content_of, field_of, normalize_envelope, unpack_batch -from bub.turn import TurnResult, TurnState -``` - -`Envelope` accepts mappings, dataclasses, Pydantic models, and objects with attributes. `field_of` and `content_of` read both mapping keys and object attributes. `normalize_envelope` produces a mutable dictionary; `unpack_batch` normalizes a hook's outbound batch. - -`TurnState` is `dict[str, Any]`. The framework creates a fresh dictionary for each inbound turn, then merges `load_state` results into it. Plugins choose which values to persist. - -`process_inbound` returns a `TurnResult` with these fields: - -| Field | Type | Contents | -| --- | --- | --- | -| `session_id` | `str` | Resolved session identity | -| `prompt` | `list[UserContent]` | Normalized input sent to the model stage | -| `model_output` | `str` | Collected text result | -| `outbounds` | `list[Envelope]` | Rendered outbound messages | -| `state` | `TurnState` | State used during this turn | - -`UserContent` comes from `bub.prompt`. The [prompt contract](/docs/reference/hooks/#prompt-content) explains its media types and conversion to legacy hook inputs. - -## Model choices - -```python -from bub.model_selection import ModelChoice, ModelOptions -``` - -`ModelChoice` has a required `id` and optional `name`, `description`, and `meta`. `ModelOptions` contains `models: list[ModelChoice]` and an optional `current_model`. - -`BubFramework.get_model_options` combines `provide_model_options` results. A channel or host presents the choices and stores the selection. +Interfaces for the core loop. They do not load application configuration. -## Streams +### Streams ```python from bub.streaming import AsyncStreamEvents, StreamEvent, StreamState @@ -67,7 +36,7 @@ When a plugin creates an `AsyncStreamEvents`, it supplies the iterator and may s Stream events are output for consumers. Agent events have a separate [delivery and control contract](/docs/concepts/events/). -## Errors and interception payloads +### Errors and interception payloads ```python from bub.errors import BubError, ErrorKind @@ -81,9 +50,48 @@ from bub.hooks.interception import ( ) ``` -`BubError.kind` identifies the error category; `as_dict()` returns a payload suitable for transport. Interception payloads are shared with `bub.core.events`. Their use by plugins, including exception handling, is defined in [Application hooks](/docs/reference/hooks/#agent-loop-hooks-pluginagenthooks). +`BubError.kind` identifies the error category; `as_dict()` returns a payload suitable for transport. Interception payloads are shared with `bub.core.events`. Their use by plugins, including exception handling, is defined in [Hooks](/docs/reference/hooks/#agent-loop-hooks-pluginagenthooks). + +## Application + +Interfaces for the application, its plugins, and its channels. + +### Messages and turn results + +Import message helpers from `bub.envelope`: + +```python +from bub.envelope import Envelope, content_of, field_of, normalize_envelope, unpack_batch +from bub.turn import TurnResult, TurnState +``` + +`Envelope` accepts mappings, dataclasses, Pydantic models, and objects with attributes. `field_of` and `content_of` read both mapping keys and object attributes. `normalize_envelope` produces a mutable dictionary; `unpack_batch` normalizes a hook's outbound batch. + +`TurnState` is `dict[str, Any]`. The framework creates a fresh dictionary for each inbound turn, then merges `load_state` results into it. Plugins choose which values to persist. + +`process_inbound` returns a `TurnResult` with these fields: + +| Field | Type | Contents | +| --- | --- | --- | +| `session_id` | `str` | Resolved session identity | +| `prompt` | `list[UserContent]` | Normalized input sent to the model stage | +| `model_output` | `str` | Collected text result | +| `outbounds` | `list[Envelope]` | Rendered outbound messages | +| `state` | `TurnState` | State used during this turn | + +`UserContent` comes from `bub.prompt`. The [prompt contract](/docs/reference/hooks/#prompt-content) explains its media types and conversion to legacy hook inputs. + +### Model choices + +```python +from bub.model_selection import ModelChoice, ModelOptions +``` + +`ModelChoice` has a required `id` and optional `name`, `description`, and `meta`. `ModelOptions` contains `models: list[ModelChoice]` and an optional `current_model`. + +`BubFramework.get_model_options` combines `provide_model_options` results. A channel or host presents the choices and stores the selection. -## Channels and routing +### Channels and routing ```python from bub.channels import Channel, Interface, Lifecycle @@ -104,7 +112,7 @@ from bub.channels.admission import AdmitDecision, SteeringInbox, TurnSnapshot `ChannelManager` implements this interface and binds it with `BubFramework.bind_channel_router`. -### Admission and steering +#### Admission and steering `TurnSnapshot` exposes `session_id`, `is_running`, `running_count`, `pending_count`, and `steering_count`. An admission hook returns `AdmitDecision(action, reason=None)`: @@ -117,7 +125,7 @@ from bub.channels.admission import AdmitDecision, SteeringInbox, TurnSnapshot A `SteeringInbox` provides async `enqueue_message(message, state)` and `drain_messages(state)`, plus synchronous `message_count(state)`. The application obtains it through `provide_steering_inbox`. -## Framework and agent entry points +### Framework and agent entry points ```python from bub import BubFramework diff --git a/website/src/content/docs/docs/reference/settings.mdx b/website/src/content/docs/docs/reference/settings.mdx index b8b137e29..5a8728326 100644 --- a/website/src/content/docs/docs/reference/settings.mdx +++ b/website/src/content/docs/docs/reference/settings.mdx @@ -1,5 +1,5 @@ --- -title: Application settings +title: Settings description: Built-in application environment variables, configuration keys, and defaults. --- diff --git a/website/src/content/docs/zh-cn/docs/app/configure.mdx b/website/src/content/docs/zh-cn/docs/app/configure.mdx index 8d5a0987f..03268d7c3 100644 --- a/website/src/content/docs/zh-cn/docs/app/configure.mdx +++ b/website/src/content/docs/zh-cn/docs/app/configure.mdx @@ -1,6 +1,6 @@ --- title: 配置 -description: 设置 Bub 应用的模型、提供商凭据和渠道。 +description: 配置 Bub 应用的模型、提供商凭据和渠道。 sidebar: order: 1 --- @@ -51,7 +51,7 @@ telegram: ## 3. 用环境变量在运行时覆盖 -可以用环境变量覆盖应用设置。常用设置采用 `BUB_` 形式: +可以用环境变量覆盖应用配置。常用设置采用 `BUB_` 形式: | 变量 | 用途 | |---|---| @@ -72,7 +72,7 @@ Republic 从 `$CODEX_HOME/auth.json` 或 `~/.codex/auth.json` 读取并刷新 Co ## 4. 配置渠道凭据 -每个渠道有自己的设置前缀。内置 Telegram 渠道读取 `BUB_TELEGRAM_*`: +每个渠道有自己的配置前缀。内置 Telegram 渠道读取 `BUB_TELEGRAM_*`: ```bash BUB_TELEGRAM_TOKEN=123456:abcdef... diff --git a/website/src/content/docs/zh-cn/docs/app/deploy.mdx b/website/src/content/docs/zh-cn/docs/app/deploy.mdx index 520ad386e..c3eb5fa50 100644 --- a/website/src/content/docs/zh-cn/docs/app/deploy.mdx +++ b/website/src/content/docs/zh-cn/docs/app/deploy.mdx @@ -141,4 +141,4 @@ docker compose exec app bub update specific-plugin # 升级指定插件 - [配置](/zh-cn/docs/app/configure/):环境变量优先级与提供商设置。 - [渠道](/zh-cn/docs/app/channels/#telegram):bot token 与白名单行为。 -- [CLI 参考](/zh-cn/docs/reference/cli/):`bub gateway`、`bub install`、`bub update` 的参数。 +- [CLI](/zh-cn/docs/reference/cli/):`bub gateway`、`bub install`、`bub update` 的参数。 diff --git a/website/src/content/docs/zh-cn/docs/app/distribution.mdx b/website/src/content/docs/zh-cn/docs/app/distribution.mdx index 80e31dc17..474d9ad21 100644 --- a/website/src/content/docs/zh-cn/docs/app/distribution.mdx +++ b/website/src/content/docs/zh-cn/docs/app/distribution.mdx @@ -72,7 +72,7 @@ my-bub = "my_bub.cli:main" 用户随后可以运行 `my-bub chat` 或 `my-bub gateway`。如果应用需要固定的工作目录,在调用 Bub 前设置;framework 会使用当前目录作为初始工作区。 -声明 `bub[app]>=0.6.0` 应用依赖。更新已有发行包时,按[升级到 0.6.0](/zh-cn/docs/reference/versions/)调整。 +声明 `bub[app]>=0.6.0` 应用依赖。更新已有发行包时,按[升级到 0.6.0](/zh-cn/docs/migration/0-6/)调整。 ## 一起分发插件和技能 diff --git a/website/src/content/docs/zh-cn/docs/app/hooks.mdx b/website/src/content/docs/zh-cn/docs/app/hooks.mdx index 401466e1a..f5c974070 100644 --- a/website/src/content/docs/zh-cn/docs/app/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/app/hooks.mdx @@ -92,7 +92,7 @@ def after_tool_call(call, result, state): print(f"tool={call.tool} duration_ms={result.duration_ms} error={result.error!r}") ``` -这些 hooks 观察已经完成的调用,包括普通异常导致的失败,但不报告取消。需要逐块处理输出时,在宿主程序或渠道中使用流接口,并遵守[关闭规则](/zh-cn/docs/reference/types/#streaming)。 +这些 hooks 观察已经完成的调用,包括普通异常导致的失败,但不报告取消。需要逐块处理输出时,在宿主程序或渠道中使用流接口,并遵守[关闭规则](/zh-cn/docs/reference/python-api/#streaming)。 如果要替换整个模型阶段,实现 `run_model` 或 `run_model_stream` 中的一个。[第一个插件](/zh-cn/docs/app/plugins/)演示了文本替换。替换实现负责自己的执行和记录行为;只有使用内置 agent 时,才会运行它的拦截 hooks。 diff --git a/website/src/content/docs/zh-cn/docs/app/terminal.mdx b/website/src/content/docs/zh-cn/docs/app/terminal.mdx index 2ea532402..37304dcc2 100644 --- a/website/src/content/docs/zh-cn/docs/app/terminal.mdx +++ b/website/src/content/docs/zh-cn/docs/app/terminal.mdx @@ -43,4 +43,4 @@ bub run ",fs.read path=README.md" 在交互式对话中也可以输入这些命令。Ctrl-X 切换 shell 模式,为输入自动添加命令前缀。 -CLI 使用前台终端,因此网关默认不启动它。网关的渠道选择和消息合并规则见[渠道](/zh-cn/docs/app/channels/),全部参数见 [CLI 参考](/zh-cn/docs/reference/cli/)。 +CLI 使用前台终端,因此网关默认不启动它。网关的渠道选择和消息合并规则见[渠道](/zh-cn/docs/app/channels/),全部参数见 [CLI](/zh-cn/docs/reference/cli/)。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/events.mdx b/website/src/content/docs/zh-cn/docs/concepts/events.mdx index 0e53a7539..e05624cdb 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/events.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/events.mdx @@ -132,6 +132,6 @@ async def mark_reviewed() -> None: 这个适配器遵循插件 hook 的顺序。单个拦截 hook 抛异常时,它接住、记日志、跳过。要拒绝一次调用,就返回对应的 decision。其他应用 hooks 按各自的出错规则处理。 -插件示例见[Hooks](/zh-cn/docs/app/hooks/)。顺序和签名见[应用 Hooks](/zh-cn/docs/reference/hooks/)。EventBus 在基础 SDK 安装里就有。 +插件示例见[Hooks](/zh-cn/docs/app/hooks/)。顺序和签名见[Hooks 参考](/zh-cn/docs/reference/hooks/)。EventBus 在基础 SDK 安装里就有。 `framework.events` 发布命令、渠道工作和用户输入,用的是 `bub.application` 里的 `CommandChanged`、`WorkChanged` 和 `PromptChanged`。订阅和取消订阅,还是 `on()` 和 `off()`。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx index 35668d759..68896e222 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx @@ -39,10 +39,10 @@ sidebar: 执行进到模型阶段后,`save_state` 就会运行,模型阶段抛异常也一样。会话解析、状态装载或 `build_prompt` 的失败发生在这个 `finally` 块之前,所以不会触发它。插件如果在这几个较早的阶段拿了资源,要自己释放。 -内置的 `save_state` 关闭单条消息 lifespan。应用另外保存选定的设置。要保存其他状态,得自己定持久化策略。 +内置的 `save_state` 关闭单条消息 lifespan。应用另外保存选定的配置项。要保存其他状态,得自己定持久化策略。 模型阶段抛出未处理异常时,框架先跑 `save_state`,然后跳过渲染和分发。外层的错误路径调用 `on_error(stage="turn", ...)`,再把异常抛出去。内置的错误观察器可以通过出站分发发一条错误回复。某个 `on_error` 观察器失败,不影响其他观察器。 ## 下一步 -用[插件设置](/zh-cn/docs/app/plugins/)注册你的实现。常见改法见[Hooks](/zh-cn/docs/app/hooks/)。精确的签名和顺序见[应用 Hooks](/zh-cn/docs/reference/hooks/)。 +用[插件设置](/zh-cn/docs/app/plugins/)注册你的实现。常见改法见[Hooks](/zh-cn/docs/app/hooks/)。精确的签名和顺序见[Hooks 参考](/zh-cn/docs/reference/hooks/)。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx index 1f76f4a72..d803fba4d 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx @@ -65,4 +65,4 @@ Bub 分两层。 - **[使用 Bub 应用](/zh-cn/docs/app/workspace/)**:先配置,再加工具、技能、插件、hooks 和渠道。 - **[构建你的 Bub Agent](/zh-cn/docs/sdk/)**:留下内核,循环周边自己写。 - **[集成](/zh-cn/docs/integrations/)**:MCP、ACP、agent plugins、本地模型、SQLite、可观测性。 -- **[参考](/zh-cn/docs/reference/)**:CLI 命令、配置项、类型、hooks、版本记录。 +- **[参考](/zh-cn/docs/reference/cli/)**:CLI 命令、配置项、Python API 和 hooks。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx index 5197ac808..e0c9c3dc6 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx @@ -101,7 +101,7 @@ dependencies = ["bub>=0.6.0"] dependencies = ["bub[app]>=0.6.0"] ``` -修改依赖后,同步项目环境。现有应用更新依赖时,参照[升级到 0.6.0](/zh-cn/docs/reference/versions/)。 +修改依赖后,同步项目环境。现有应用更新依赖时,参照[升级到 0.6.0](/zh-cn/docs/migration/0-6/)。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx index 231864245..d70031af5 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx @@ -25,7 +25,7 @@ sidebar: bub install bub-mcp@main ``` -`bub install` 使用当前 Bub 所在的环境。插件来源与依赖处理方式见 [CLI 参考](/zh-cn/docs/reference/cli/#bub-install)。 +`bub install` 使用当前 Bub 所在的环境。插件来源与依赖处理方式见 [CLI](/zh-cn/docs/reference/cli/#bub-install)。 [Agent Plugins](/zh-cn/docs/integrations/agent-plugins/) 也可以加载技能和自带的 MCP 服务器,与这里的服务器一起使用。该集成使用独立的生命周期渠道,以及包含插件名的工具名称。 diff --git a/website/src/content/docs/zh-cn/docs/reference/versions.mdx b/website/src/content/docs/zh-cn/docs/migration/0-6.mdx similarity index 100% rename from website/src/content/docs/zh-cn/docs/reference/versions.mdx rename to website/src/content/docs/zh-cn/docs/migration/0-6.mdx diff --git a/website/src/content/docs/zh-cn/docs/reference/cli.mdx b/website/src/content/docs/zh-cn/docs/reference/cli.mdx index 5d6a1204d..3459e303e 100644 --- a/website/src/content/docs/zh-cn/docs/reference/cli.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/cli.mdx @@ -1,5 +1,5 @@ --- -title: CLI 参考 +title: CLI description: 应用内置命令的选项、默认值和行为。 --- diff --git a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx index 00c1c6db1..e7545e924 100644 --- a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx @@ -1,5 +1,5 @@ --- -title: 应用 Hooks +title: Hooks description: Bub 插件的 hook 签名、调用规则和资源生命周期。 --- diff --git a/website/src/content/docs/zh-cn/docs/reference/index.mdx b/website/src/content/docs/zh-cn/docs/reference/index.mdx deleted file mode 100644 index bd2cb5f51..000000000 --- a/website/src/content/docs/zh-cn/docs/reference/index.mdx +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: 参考 -description: 查阅 Agent 接口、共享能力契约和 Bub 应用 API。 ---- - -按代码中用到的接口查阅。Agent 接口与应用扩展点各有职责;[概览](/zh-cn/docs/concepts/)说明它们之间的关系。 - - - -## Agent 接口 - -| 接口 | 参考 | -| --- | --- | -| Agent 构造与模型客户端 | [Agent 参数](/zh-cn/docs/sdk/options/#agent-parameters)与[模型](/zh-cn/docs/sdk/options/#models) | -| 运行与流的生命周期 | [流与生命周期](/zh-cn/docs/sdk/#streams-and-lifecycle) | -| 提示词片段与循环定制 | [系统提示词](/zh-cn/docs/sdk/options/#system-prompt)与[定制循环](/zh-cn/docs/sdk/options/#customize-the-loop) | -| 工具与 CodeMode | [工具](/zh-cn/docs/sdk/tools/) | -| Tape、存储与上下文 | [Tape 与上下文](/zh-cn/docs/concepts/tape/) | -| 事件分发与执行决策 | [Agent 事件](/zh-cn/docs/concepts/events/) | - - - -## Bub 应用 - -| 接口 | 参考 | -| --- | --- | -| 插件 hook 的名称、签名与返回值 | [Hooks](/zh-cn/docs/reference/hooks/) | -| CLI 命令与选项 | [CLI](/zh-cn/docs/reference/cli/) | -| 环境变量与配置项 | [配置](/zh-cn/docs/reference/settings/) | -| Framework、渠道、信封与应用类型 | [应用契约](/zh-cn/docs/reference/types/) | - -[升级到 0.6.0](/zh-cn/docs/reference/versions/)说明现有应用需要调整的依赖。 diff --git a/website/src/content/docs/zh-cn/docs/reference/types.mdx b/website/src/content/docs/zh-cn/docs/reference/python-api.mdx similarity index 88% rename from website/src/content/docs/zh-cn/docs/reference/types.mdx rename to website/src/content/docs/zh-cn/docs/reference/python-api.mdx index 1f84b501d..ae6ab056f 100644 --- a/website/src/content/docs/zh-cn/docs/reference/types.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/python-api.mdx @@ -1,48 +1,17 @@ --- -title: 应用类型与生命周期 -description: Bub 应用使用的消息、轮次、流和渠道接口。 +title: Python API +description: 核心 agent 与 Bub 应用的接口,按所属范围分组。 --- -编写插件、接入渠道或嵌入 Bub 应用时,使用这里的类型。公共 agent 能力分别见[工具](/zh-cn/docs/sdk/tools/)、[Tape 与上下文](/zh-cn/docs/concepts/tape/)和[事件](/zh-cn/docs/concepts/events/)。 +按接口的归属查阅。核心接口在自己的 Python 程序里配好模型和工具即可使用;应用接口用于运行 Bub 应用、编写插件或接入渠道。具体用法见[工具](/zh-cn/docs/sdk/tools/)、[Tape 与上下文](/zh-cn/docs/concepts/tape/)和[事件](/zh-cn/docs/concepts/events/)。 -## 消息与轮次结果 +## 核心 -从 `bub.envelope` 导入消息辅助函数: - -```python -from bub.envelope import Envelope, content_of, field_of, normalize_envelope, unpack_batch -from bub.turn import TurnResult, TurnState -``` - -`Envelope` 接受映射、dataclass、Pydantic model 和具有属性的对象。`field_of` 与 `content_of` 可以读取映射的键和对象的属性。`normalize_envelope` 生成可变字典,`unpack_batch` 规范化 hook 返回的出站消息批次。 - -`TurnState` 是 `dict[str, Any]`。Framework 为每轮入站消息创建新字典,再合并 `load_state` 返回值。插件自行决定持久化哪些值。 - -`process_inbound` 返回的 `TurnResult` 包含这些字段: - -| 字段 | 类型 | 内容 | -| --- | --- | --- | -| `session_id` | `str` | 解析后的会话标识 | -| `prompt` | `list[UserContent]` | 交给模型阶段的规范化输入 | -| `model_output` | `str` | 收集后的文本结果 | -| `outbounds` | `list[Envelope]` | 生成的出站消息 | -| `state` | `TurnState` | 本轮使用的状态 | - -`UserContent` 来自 `bub.prompt`。[提示词契约](/zh-cn/docs/reference/hooks/#prompt-content)说明媒体类型,以及到旧式 hook 输入的转换。 - -## 模型选项 - -```python -from bub.model_selection import ModelChoice, ModelOptions -``` - -`ModelChoice` 必须提供 `id`,可选字段为 `name`、`description` 和 `meta`。`ModelOptions` 包含 `models: list[ModelChoice]` 和可选的 `current_model`。 - -`BubFramework.get_model_options` 合并 `provide_model_options` 返回的结果。渠道或宿主程序负责展示选项和保存选择。 +核心循环使用的接口,不读取应用配置。 -## 流 +### 流 ```python from bub.streaming import AsyncStreamEvents, StreamEvent, StreamState @@ -67,7 +36,7 @@ async def consume(stream: AsyncStreamEvents) -> None: 流事件向消费方交付输出。Agent 事件有独立的[分发与控制契约](/zh-cn/docs/concepts/events/)。 -## 错误与拦截载荷 +### 错误与拦截载荷 ```python from bub.errors import BubError, ErrorKind @@ -81,9 +50,48 @@ from bub.hooks.interception import ( ) ``` -`BubError.kind` 表示错误类别,`as_dict()` 返回适合传输的载荷。拦截载荷与 `bub.core.events` 共用类型。插件如何使用它们,以及异常如何处理,见[应用 Hooks](/zh-cn/docs/reference/hooks/#agent-loop-hooks-pluginagenthooks)。 +`BubError.kind` 表示错误类别,`as_dict()` 返回适合传输的载荷。拦截载荷与 `bub.core.events` 共用类型。插件如何使用它们,以及异常如何处理,见 [Hooks](/zh-cn/docs/reference/hooks/#agent-loop-hooks-pluginagenthooks)。 + +## 应用 + +应用、插件与渠道使用的接口。 + +### 消息与轮次结果 + +从 `bub.envelope` 导入消息辅助函数: + +```python +from bub.envelope import Envelope, content_of, field_of, normalize_envelope, unpack_batch +from bub.turn import TurnResult, TurnState +``` + +`Envelope` 接受映射、dataclass、Pydantic model 和具有属性的对象。`field_of` 与 `content_of` 可以读取映射的键和对象的属性。`normalize_envelope` 生成可变字典,`unpack_batch` 规范化 hook 返回的出站消息批次。 + +`TurnState` 是 `dict[str, Any]`。Framework 为每轮入站消息创建新字典,再合并 `load_state` 返回值。插件自行决定持久化哪些值。 + +`process_inbound` 返回的 `TurnResult` 包含这些字段: + +| 字段 | 类型 | 内容 | +| --- | --- | --- | +| `session_id` | `str` | 解析后的会话标识 | +| `prompt` | `list[UserContent]` | 交给模型阶段的规范化输入 | +| `model_output` | `str` | 收集后的文本结果 | +| `outbounds` | `list[Envelope]` | 生成的出站消息 | +| `state` | `TurnState` | 本轮使用的状态 | + +`UserContent` 来自 `bub.prompt`。[提示词契约](/zh-cn/docs/reference/hooks/#prompt-content)说明媒体类型,以及到旧式 hook 输入的转换。 + +### 模型选项 + +```python +from bub.model_selection import ModelChoice, ModelOptions +``` + +`ModelChoice` 必须提供 `id`,可选字段为 `name`、`description` 和 `meta`。`ModelOptions` 包含 `models: list[ModelChoice]` 和可选的 `current_model`。 + +`BubFramework.get_model_options` 合并 `provide_model_options` 返回的结果。渠道或宿主程序负责展示选项和保存选择。 -## 渠道与路由 +### 渠道与路由 ```python from bub.channels import Channel, Interface, Lifecycle @@ -104,7 +112,7 @@ from bub.channels.admission import AdmitDecision, SteeringInbox, TurnSnapshot `ChannelManager` 实现这个接口,并通过 `BubFramework.bind_channel_router` 绑定。 -### 消息准入与 steering +#### 消息准入与 steering `TurnSnapshot` 提供 `session_id`、`is_running`、`running_count`、`pending_count` 和 `steering_count`。准入 hook 返回 `AdmitDecision(action, reason=None)`: @@ -117,7 +125,7 @@ from bub.channels.admission import AdmitDecision, SteeringInbox, TurnSnapshot `SteeringInbox` 提供异步的 `enqueue_message(message, state)`、`drain_messages(state)`,以及同步的 `message_count(state)`。应用通过 `provide_steering_inbox` 取得它。 -## Framework 与 Agent 入口 +### Framework 与 Agent 入口 ```python from bub import BubFramework diff --git a/website/src/content/docs/zh-cn/docs/reference/settings.mdx b/website/src/content/docs/zh-cn/docs/reference/settings.mdx index e15adc136..9bf933ec7 100644 --- a/website/src/content/docs/zh-cn/docs/reference/settings.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/settings.mdx @@ -1,9 +1,9 @@ --- -title: 应用设置 +title: 配置项 description: 应用内置环境变量、配置字段和默认值。 --- -这些设置用于配置 Bub 应用。表格列出内置的 `BUB_*` 变量及其 YAML 字段,插件设置由各插件文档说明。操作步骤见[配置](/zh-cn/docs/app/configure/)。直接构造的 `core.Agent` 通过 Python 参数接收组件,不加载这些配置。 +这些配置项控制 Bub 应用的行为。表格列出内置的 `BUB_*` 变量及其 YAML 字段,插件的配置项由各插件文档说明。操作步骤见[配置](/zh-cn/docs/app/configure/)。直接构造的 `core.Agent` 通过 Python 参数接收组件,不加载这些配置。 ## 优先级 From 4831d6a04dfaa4e5e5c78ad3848f50f71e7c0eb7 Mon Sep 17 00:00:00 2001 From: 100gle Date: Wed, 21 Oct 2026 17:36:19 +0800 Subject: [PATCH 08/28] docs(migration): name the 0.6 note v0-6 A file name of 0.6 turns into no usable slug, and 0-6 reads like a range. The v prefix keeps the version recognisable and leaves room for v0-7. --- website/astro.config.mjs | 6 +++--- website/src/content/docs/docs/app/distribution.mdx | 2 +- website/src/content/docs/docs/getting-started/install.mdx | 2 +- .../src/content/docs/docs/migration/{0-6.mdx => v0-6.mdx} | 0 website/src/content/docs/zh-cn/docs/app/distribution.mdx | 2 +- .../src/content/docs/zh-cn/docs/getting-started/install.mdx | 2 +- .../content/docs/zh-cn/docs/migration/{0-6.mdx => v0-6.mdx} | 0 7 files changed, 7 insertions(+), 7 deletions(-) rename website/src/content/docs/docs/migration/{0-6.mdx => v0-6.mdx} (100%) rename website/src/content/docs/zh-cn/docs/migration/{0-6.mdx => v0-6.mdx} (100%) diff --git a/website/astro.config.mjs b/website/astro.config.mjs index da2f55700..a20d74e55 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -113,10 +113,10 @@ export default defineConfig({ '/zh-cn/docs/tutorials/tapestore-sqlalchemy': '/zh-cn/docs/integrations/storage/', '/docs/reference/types': '/docs/reference/python-api/', '/docs/reference': '/docs/reference/cli/', - '/docs/reference/versions': '/docs/migration/0-6/', + '/docs/reference/versions': '/docs/migration/v0-6/', '/zh-cn/docs/reference/types': '/zh-cn/docs/reference/python-api/', '/zh-cn/docs/reference': '/zh-cn/docs/reference/cli/', - '/zh-cn/docs/reference/versions': '/zh-cn/docs/migration/0-6/', + '/zh-cn/docs/reference/versions': '/zh-cn/docs/migration/v0-6/', }, env: { schema: { @@ -298,7 +298,7 @@ export default defineConfig({ { label: 'Migration', translations: { 'zh-CN': '迁移' }, - items: ['docs/migration/0-6'], + items: ['docs/migration/v0-6'], }, ], }), diff --git a/website/src/content/docs/docs/app/distribution.mdx b/website/src/content/docs/docs/app/distribution.mdx index 87893ff83..99d497a6d 100644 --- a/website/src/content/docs/docs/app/distribution.mdx +++ b/website/src/content/docs/docs/app/distribution.mdx @@ -72,7 +72,7 @@ my-bub = "my_bub.cli:main" Users can then run `my-bub chat` or `my-bub gateway`. Set a product-specific working directory before calling Bub if your application requires one; the framework takes its initial workspace from the current directory. -Declare `bub[app]>=0.6.0` as the application dependency. When upgrading an existing distribution, follow [Upgrade to 0.6.0](/docs/migration/0-6/). +Declare `bub[app]>=0.6.0` as the application dependency. When upgrading an existing distribution, follow [Upgrade to 0.6.0](/docs/migration/v0-6/). ## Bundle plugins and skills diff --git a/website/src/content/docs/docs/getting-started/install.mdx b/website/src/content/docs/docs/getting-started/install.mdx index f5233299a..49c1a147c 100644 --- a/website/src/content/docs/docs/getting-started/install.mdx +++ b/website/src/content/docs/docs/getting-started/install.mdx @@ -101,7 +101,7 @@ For a package that imports the builtin agent, Framework, plugins, skills, or cha dependencies = ["bub[app]>=0.6.0"] ``` -Synchronize your project after editing its dependencies. Existing applications should follow [Upgrade to 0.6.0](/docs/migration/0-6/) when updating the dependency. +Synchronize your project after editing its dependencies. Existing applications should follow [Upgrade to 0.6.0](/docs/migration/v0-6/) when updating the dependency. diff --git a/website/src/content/docs/docs/migration/0-6.mdx b/website/src/content/docs/docs/migration/v0-6.mdx similarity index 100% rename from website/src/content/docs/docs/migration/0-6.mdx rename to website/src/content/docs/docs/migration/v0-6.mdx diff --git a/website/src/content/docs/zh-cn/docs/app/distribution.mdx b/website/src/content/docs/zh-cn/docs/app/distribution.mdx index 474d9ad21..74741aebb 100644 --- a/website/src/content/docs/zh-cn/docs/app/distribution.mdx +++ b/website/src/content/docs/zh-cn/docs/app/distribution.mdx @@ -72,7 +72,7 @@ my-bub = "my_bub.cli:main" 用户随后可以运行 `my-bub chat` 或 `my-bub gateway`。如果应用需要固定的工作目录,在调用 Bub 前设置;framework 会使用当前目录作为初始工作区。 -声明 `bub[app]>=0.6.0` 应用依赖。更新已有发行包时,按[升级到 0.6.0](/zh-cn/docs/migration/0-6/)调整。 +声明 `bub[app]>=0.6.0` 应用依赖。更新已有发行包时,按[升级到 0.6.0](/zh-cn/docs/migration/v0-6/)调整。 ## 一起分发插件和技能 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx index e0c9c3dc6..99e962c89 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx @@ -101,7 +101,7 @@ dependencies = ["bub>=0.6.0"] dependencies = ["bub[app]>=0.6.0"] ``` -修改依赖后,同步项目环境。现有应用更新依赖时,参照[升级到 0.6.0](/zh-cn/docs/migration/0-6/)。 +修改依赖后,同步项目环境。现有应用更新依赖时,参照[升级到 0.6.0](/zh-cn/docs/migration/v0-6/)。 diff --git a/website/src/content/docs/zh-cn/docs/migration/0-6.mdx b/website/src/content/docs/zh-cn/docs/migration/v0-6.mdx similarity index 100% rename from website/src/content/docs/zh-cn/docs/migration/0-6.mdx rename to website/src/content/docs/zh-cn/docs/migration/v0-6.mdx From 6a29b03c99034eb2afa9f90db5c2554ed06a3fa2 Mon Sep 17 00:00:00 2001 From: 100gle Date: Wed, 21 Oct 2026 17:48:25 +0800 Subject: [PATCH 09/28] docs(zh-cn): keep the domain terms in English MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Chinese pages translated `turn` as 轮次, while `tape`, `hook`, `prompt`, and `envelope` stayed in English. A translated term breaks the link to `TurnState`, `TurnResult`, and `on_error(stage="turn")`, and 轮次 reads like a scheduling ordinal rather than a unit of work. Use `turn` in the prose instead, and define it once on the Turn page. Fold the same decision into the pages that leaned on 本轮 / 一轮 for the same concept, and keep 一轮 only where it means a CLI chat round or a `run`. --- website/TRANSLATING.md | 29 +++++++++++++++++++ .../content/docs/zh-cn/docs/app/channels.mdx | 8 ++--- .../docs/zh-cn/docs/app/distribution.mdx | 2 +- .../src/content/docs/zh-cn/docs/app/hooks.mdx | 4 +-- .../content/docs/zh-cn/docs/app/plugins.mdx | 4 +-- .../content/docs/zh-cn/docs/app/skills.mdx | 2 +- .../src/content/docs/zh-cn/docs/app/tools.mdx | 20 ++++++------- .../docs/zh-cn/docs/concepts/events.mdx | 6 ++-- .../zh-cn/docs/concepts/extension-points.mdx | 4 +-- .../docs/zh-cn/docs/concepts/index.mdx | 8 ++--- .../content/docs/zh-cn/docs/concepts/tape.mdx | 2 +- .../content/docs/zh-cn/docs/concepts/turn.mdx | 10 +++---- .../docs/zh-cn/docs/getting-started/index.mdx | 4 +-- .../zh-cn/docs/getting-started/quickstart.mdx | 2 +- .../docs/zh-cn/docs/integrations/acp.mdx | 10 +++---- .../zh-cn/docs/integrations/local-models.mdx | 4 +-- .../docs/zh-cn/docs/integrations/mcp.mdx | 8 ++--- .../zh-cn/docs/integrations/observability.mdx | 4 +-- .../content/docs/zh-cn/docs/reference/cli.mdx | 2 +- .../docs/zh-cn/docs/reference/hooks.mdx | 4 +-- .../docs/zh-cn/docs/reference/python-api.mdx | 6 ++-- .../docs/zh-cn/docs/reference/settings.mdx | 2 +- .../src/content/docs/zh-cn/docs/sdk/index.md | 4 +-- .../content/docs/zh-cn/docs/sdk/options.mdx | 8 ++--- .../src/content/docs/zh-cn/docs/sdk/tools.mdx | 6 ++-- website/src/data/landing-page.ts | 2 +- 26 files changed, 97 insertions(+), 68 deletions(-) create mode 100644 website/TRANSLATING.md diff --git a/website/TRANSLATING.md b/website/TRANSLATING.md new file mode 100644 index 000000000..d386de5bf --- /dev/null +++ b/website/TRANSLATING.md @@ -0,0 +1,29 @@ +# Translating the docs + +Applies to every locale under `src/content/docs/`. The English source is `src/content/docs/docs/`. Keep its paragraph and heading structure; do not merge, split, or reorder. + +## Glossary + +A term that names code keeps its spelling. Do not invent a local name. A new locale adds a column. + +Anything not listed is an ordinary technical word. Translate it the usual way; judge each case. Keep one rendering per page. + +| Term | zh-cn | Note | +| --- | --- | --- | +| `turn` | `turn` | One inbound message and all its work. Never 轮次, 回合. | +| `tape` | `tape` | The conversation record. Never 磁带. | +| `anchor` | `anchor` | Where the context starts. | +| `handoff` | `handoff` | Writes a new anchor. | +| `state` | `state` | The dictionary a turn shares. Type `TurnState`. | +| `steering` | `steering` | | +| `hook` | `hook` | Never 钩子. | +| `envelope` | `envelope` | Never 信封. | +| `trace` | `trace` | Never 轨迹, 追踪. | +| `CodeMode` | `CodeMode` | The class. The feature is `code mode`. Never 代码模式. | + +## Punctuation (zh-cn) + +- Use full-width `,。;:!?、` and `“”`. +- Put one space between Chinese and Latin letters, digits, or inline code. +- Use `……` for an ellipsis and `——` for a dash. +- Put no space next to full-width punctuation, and none inside bold or italic text. diff --git a/website/src/content/docs/zh-cn/docs/app/channels.mdx b/website/src/content/docs/zh-cn/docs/app/channels.mdx index 82606f044..7d94a2b0c 100644 --- a/website/src/content/docs/zh-cn/docs/app/channels.mdx +++ b/website/src/content/docs/zh-cn/docs/app/channels.mdx @@ -51,7 +51,7 @@ BUB_ENABLED_CHANNELS=telegram ## 防抖与批处理 -非 CLI 的渠道可以把一串短消息合成一轮。这三个变量控制防抖: +非 CLI 的渠道可以把一串短消息合成一个 turn。这三个变量控制防抖: | 变量 | 默认值 | 含义 | |---|---|---| @@ -142,18 +142,18 @@ telegram.start polling 这个渠道用 `telegram:` 作为会话 ID。同一个用户既在私聊又在群里,两边的会话也是分开的。 - `/start` 由内部处理。机器人回一句固定欢迎语;不在白名单的聊天里,回的是"不允许"。 -- `/bub ` 会被规范化。去掉 `/bub ` 前缀,剩下的当作 prompt。 +- `/bub ` 会被规范化。去掉 `/bub ` 前缀,剩下的当作提示词。 - 私聊里,所有非命令消息都会被收下。 - 群聊里,所有非命令消息都会到渠道。提到 `@`、带关键字 `bub`、或者回复机器人的消息,算作活跃。其他群消息被忽略,除非这个会话在 `BUB_ACTIVE_TIME_WINDOW` 内还有对话开着,那它们就并进这一批。 ### 出站消息 -普通消息进轮次流水线之前会被设成 `output_channel="null"`。所以模型随口一句的回答不会被直接发回 Telegram。回复、进度更新、改消息,走的是内置的 `telegram` 技能。 +普通消息进 turn 流水线之前会被设成 `output_channel="null"`。所以模型随口一句的回答不会被直接发回 Telegram。回复、进度更新、改消息,走的是内置的 `telegram` 技能。 逗号命令不一样。它们保留 `output_channel="telegram"`,工具输出会发回原来的 `chat_id`。内容是带 `message` 字段的 JSON 时,只发这个字段;否则发原始内容。空载荷直接丢掉。 ## 下一步 -- [终端](/zh-cn/docs/app/terminal/):REPL 和单次轮次。 +- [终端](/zh-cn/docs/app/terminal/):REPL 和单次 turn。 - [部署](/zh-cn/docs/app/deploy/):把机器人放进容器,状态持久化。 - [设置参考](/zh-cn/docs/reference/settings/):全部渠道变量。 diff --git a/website/src/content/docs/zh-cn/docs/app/distribution.mdx b/website/src/content/docs/zh-cn/docs/app/distribution.mdx index 74741aebb..91b009cf8 100644 --- a/website/src/content/docs/zh-cn/docs/app/distribution.mdx +++ b/website/src/content/docs/zh-cn/docs/app/distribution.mdx @@ -13,7 +13,7 @@ sidebar: ## 嵌入应用 -用 `BubFramework.process_inbound` 执行应用的完整轮次:解析会话、加载状态、准备提示词、执行、保存状态、生成并发送出站消息。 +用 `BubFramework.process_inbound` 执行应用的完整 turn:解析会话、加载状态、准备提示词、执行、保存状态、生成并发送出站消息。 ```python import asyncio diff --git a/website/src/content/docs/zh-cn/docs/app/hooks.mdx b/website/src/content/docs/zh-cn/docs/app/hooks.mdx index f5c974070..bd64d2129 100644 --- a/website/src/content/docs/zh-cn/docs/app/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/app/hooks.mdx @@ -96,7 +96,7 @@ def after_tool_call(call, result, state): 如果要替换整个模型阶段,实现 `run_model` 或 `run_model_stream` 中的一个。[第一个插件](/zh-cn/docs/app/plugins/)演示了文本替换。替换实现负责自己的执行和记录行为;只有使用内置 agent 时,才会运行它的拦截 hooks。 -## 跨轮次保留应用状态 +## 跨 turn 保留应用状态 Framework 为每轮入站消息构建新的状态字典。使用 `load_state` 和 `save_state`,保留应用自己的值: @@ -120,7 +120,7 @@ class TurnCounter: turn_counter = TurnCounter() ``` -这个内存示例统计模型阶段退出的次数,包括失败。进程退出后计数会丢失,并且它假定同一会话一次只处理一轮。生产环境应使用持久化存储,并选择合适的并发策略。 +这个内存示例统计模型阶段退出的次数,包括失败。进程退出后计数会丢失,并且它假定同一会话一次只处理一个 turn。生产环境应使用持久化存储,并选择合适的并发策略。 `save_state` 位于模型阶段的 `finally` 块中,前提是会话解析、状态加载和提示词准备已经成功。这些准备阶段发生错误时,会跳过保存 hook。会话历史另有 [Tape 生命周期](/zh-cn/docs/concepts/tape/#state)。 diff --git a/website/src/content/docs/zh-cn/docs/app/plugins.mdx b/website/src/content/docs/zh-cn/docs/app/plugins.mdx index 1f8253ad1..76fedee91 100644 --- a/website/src/content/docs/zh-cn/docs/app/plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/app/plugins.mdx @@ -5,7 +5,7 @@ sidebar: order: 7 --- -插件可以改变 Bub 应用处理任务的方式。本页先带你写一个回声插件,无需模型凭据就能检查插件发现和轮次处理,再说明入口点的几种形式、工具注册、分享方式,以及用其他语言实现的办法。 +插件可以改变 Bub 应用处理任务的方式。本页先带你写一个回声插件,无需模型凭据就能检查插件发现和 turn 处理,再说明入口点的几种形式、工具注册、分享方式,以及用其他语言实现的办法。 Python 包在 `bub` 入口点组中声明目标,Bub 导入这个目标,并注册其中的 hook 实现。你也可以在自己的服务中复用现有插件,继续使用宿主自己的调用入口;参见[复用插件](/zh-cn/docs/sdk/reuse-plugins/)。 @@ -153,7 +153,7 @@ class MyPlugin: from . import tools # noqa: F401 ``` -应用创建 `BubAgent` 时会获取已注册工具的快照。[工具](/zh-cn/docs/app/tools/)会说明注册、延迟加载和代码模式。 +应用创建 `BubAgent` 时会获取已注册工具的快照。[工具](/zh-cn/docs/app/tools/)会说明注册、延迟加载和 code mode。 ## 分享插件 diff --git a/website/src/content/docs/zh-cn/docs/app/skills.mdx b/website/src/content/docs/zh-cn/docs/app/skills.mdx index 92aeeb256..05caa2646 100644 --- a/website/src/content/docs/zh-cn/docs/app/skills.mdx +++ b/website/src/content/docs/zh-cn/docs/app/skills.mdx @@ -48,7 +48,7 @@ bub run ",skill name=repo-map" bub run '使用 $repo-map 技能,然后列出你找到的顶层模块。' ``` -`$repo-map` 会把该技能的指令加入本轮系统提示词。模型随后使用应用中可用的工具执行这些步骤。这个例子需要在执行环境中安装 `rg`。 +`$repo-map` 会把该技能的指令加入该 turn 的系统提示词。模型随后使用应用中可用的工具执行这些步骤。这个例子需要在执行环境中安装 `rg`。 ## 技能文件字段 diff --git a/website/src/content/docs/zh-cn/docs/app/tools.mdx b/website/src/content/docs/zh-cn/docs/app/tools.mdx index ac15c8a17..7dbd134b7 100644 --- a/website/src/content/docs/zh-cn/docs/app/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/app/tools.mdx @@ -40,11 +40,11 @@ class OrdersPlugin: 运行 `bub hooks` 检查插件是否实现了 `provide_tools`。`orders.get` 在模型中的名称是 `orders_get`。 -## 选择本轮可用的工具 +## 选择 turn 可用的工具 -调用 `BubAgent.run_stream` 时,传入 `allowed_tools` 限制本轮模型可用的工具。它接受运行时名称或模型名称。`None` 允许全部实例工具,空集合不允许任何工具。未知名称会抛出 `ValueError`。 +调用 `BubAgent.run_stream` 时,传入 `allowed_tools` 限制该 turn 里模型可用的工具。它接受运行时名称或模型名称。`None` 允许全部实例工具,空集合不允许任何工具。未知名称会抛出 `ValueError`。 -这个筛选适用于模型和代码模式中的工具。逗号命令直接使用 agent 实例的工具。如果规则也需要约束命令执行,使用[工具调用 decision](/zh-cn/docs/app/hooks/#control-calls)。 +这个筛选适用于模型和 code mode 中的工具。逗号命令直接使用 agent 实例的工具。如果规则也需要约束命令执行,使用[工具调用 decision](/zh-cn/docs/app/hooks/#control-calls)。 @@ -62,32 +62,32 @@ def get_order(order_id: str) -> dict[str, str]: return {"id": order_id, "status": "shipped"} ``` -`BubAgent` 在系统提示词中列出允许使用的延迟工具;只要还有这样的工具,就提供 `tool_describe`。模型调用它来加载定义。应用记录 `tool.loaded` 条目,让已加载的工具一直可用,直到 tape 被重置:加载后的后续步骤中模型即可调用它,之后的轮次会直接提供它。对已加载的工具再次调用 `tool_describe` 时,它的名字会出现在 `already_loaded` 中,不再返回定义。 +`BubAgent` 在系统提示词中列出允许使用的延迟工具;只要还有这样的工具,就提供 `tool_describe`。模型调用它来加载定义。应用记录 `tool.loaded` 条目,让已加载的工具一直可用,直到 tape 被重置:加载后的后续步骤中模型即可调用它,之后的 turn 会直接提供它。对已加载的工具再次调用 `tool_describe` 时,它的名字会出现在 `already_loaded` 中,不再返回定义。 -本轮允许列表同时约束工具的展示和加载。代码模式中,允许使用的 `auto` 和 `code` 工具立即可供 Python 调用;延迟策略只作用于仍直接提供给模型的工具。 +turn 的允许列表同时约束工具的展示和加载。code mode 中,允许使用的 `auto` 和 `code` 工具立即可供 Python 调用;延迟策略只作用于仍直接提供给模型的工具。 -## 在应用中使用代码模式 +## 在应用中使用 code mode -为当前会话启用代码模式: +为当前会话启用 code mode: ```text ,code_mode enable=true ``` -设置从下一轮开始生效,并被记录以便重启后恢复。使用 `,code_mode enable=false` 关闭。直接调用 `BubAgent` 并传入自有状态时,可以为本次运行设置 `state["code_mode"] = True`。 +设置从下一个 turn 开始生效,并被记录以便重启后恢复。使用 `,code_mode enable=false` 关闭。直接调用 `BubAgent` 并传入自有状态时,可以为本次运行设置 `state["code_mode"] = True`。 允许使用 `run_code` 时,应用按 `exposure` 选择工具: -| `exposure` | 代码模式关闭 | 代码模式开启 | +| `exposure` | code mode 关闭 | code mode 开启 | | --- | --- | --- | | `auto` | 直接提供给模型 | 通过 `run_code` 调用 | | `direct` | 直接提供给模型 | 直接提供,不出现在 `tools.*` 下 | | `code` | 不提供给模型 | 通过 `run_code` 调用 | | `command` | 仅逗号命令 | 仅逗号命令 | -如果排除了 `run_code`,本轮按关闭代码模式时的规则选择工具。应用把 Python 工具声明文件写入 `/codemode/`,并把路径加入系统提示词。`await tools.orders_get(order_id="123")` 这样的 Python 调用使用已选工具,并经过应用的拦截 hooks。 +如果排除了 `run_code`,该 turn 按关闭 code mode 时的规则选择工具。应用把 Python 工具声明文件写入 `/codemode/`,并把路径加入系统提示词。`await tools.orders_get(order_id="123")` 这样的 Python 调用使用已选工具,并经过应用的拦截 hooks。 应用把 CodeMode 接到会话的执行环境和模型设置上。代码可以使用 [CodeMode](/zh-cn/docs/sdk/tools/#code-mode) 中介绍的 Republic bridge 和媒体附加工具。通过 bridge 使用模型时,凭据保留在宿主侧;默认的本地执行环境仍在这个宿主上运行代码。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/events.mdx b/website/src/content/docs/zh-cn/docs/concepts/events.mdx index e05624cdb..90e7db64b 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/events.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/events.mdx @@ -67,11 +67,11 @@ def require_refund_permission(event: ToolCallStarted) -> None: | 事件 | 什么时候发 | 你能做什么 | | --- | --- | --- | -| `RunStarted` | 一次模型运行开始。 | 读 prompt、模型和 metadata。 | +| `RunStarted` | 一次模型运行开始。 | 读提示词、模型和 metadata。 | | `RunFinished` | 这次运行的流关闭。 | 读状态、步骤数、耗时和错误。 | | `StepStarted` | 一个模型步骤开始。 | 读进度。 | | `StepFinished` | 一个模型步骤结束。 | 读进度。`status == "continue"` 时,设 `next_prompt`,给下一步加一条用户消息。 | -| `MessagesPending` | 步骤构建模型请求时,或步骤结束时没有调工具。 | 往 `messages` 追加用户消息,排在下一个 prompt 之前。在步骤结束时加的消息,会让运行继续。 | +| `MessagesPending` | 步骤构建模型请求时,或步骤结束时没有调工具。 | 往 `messages` 追加用户消息,排在下一个提示词之前。在步骤结束时加的消息,会让运行继续。 | | `LLMCallStarted` | 调供应商之前。 | 换掉 `request`,或设 `LLMCallDecision.finish(text)`。 | | `LLMCallFinished` | 供应商调用完成,或抛异常。 | 读累积的结果。取消和消费方关流,这里不报。 | | `ToolCallStarted` | 工具执行之前。 | 换掉 `call`,拒绝调用,或换掉结果。 | @@ -79,7 +79,7 @@ def require_refund_permission(event: ToolCallStarted) -> None: | `ChatFinished` | 这一步的往返可以记录了。 | 读这一次往返。 | | `CommandFinished` | 直接命令完成或失败。 | 读命令、状态和输出。 | -命令 prompt 直接跑工具,会发出工具事件和 `CommandFinished`。见[命令](/zh-cn/docs/sdk/tools/#commands)。 +命令提示词直接跑工具,会发出工具事件和 `CommandFinished`。见[命令](/zh-cn/docs/sdk/tools/#commands)。 所有 agent 事件都带本轮的 `state`。从 `TapeEvent` 派生的事件还带当前 tape。工具事件在调用有上下文时,可以从 `event.call.context` 取到。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx index 10b54cc9d..908aa869c 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx @@ -26,8 +26,8 @@ agent 也可以用一条[文本命令](/zh-cn/docs/sdk/tools/#commands)调工具 ## 消息与状态 -渠道收一个入站 envelope,发出站 envelope。应用用 envelope 带内容、路由和会话标识。各个 hooks 共用当前这一轮的 `TurnState` 字典。 +渠道收一个入站 envelope,发出站 envelope。应用用 envelope 带内容、路由和会话标识。各个 hooks 共用当前 turn 的 `TurnState` 字典。 -[轮次](/zh-cn/docs/concepts/turn/)先把状态和 prompt 准备好,再调 agent。直接用 SDK 的人,自己把 prompt 和 state 交给 Agent。 +[Turn](/zh-cn/docs/concepts/turn/)先把状态和提示词准备好,再调 agent。直接用 SDK 的人,自己把提示词和 state 交给 Agent。 先按要用的能力选依赖,再按宿主选调用入口。一个 Python 服务可以复用应用插件,同时保留自己的请求处理和流。[复用插件](/zh-cn/docs/sdk/reuse-plugins/)讲怎么直接用组件,也讲 framework hooks 在嵌入的宿主里怎么接。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/index.mdx b/website/src/content/docs/zh-cn/docs/concepts/index.mdx index 48a8c1c80..ceb70b74c 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/index.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/index.mdx @@ -5,7 +5,7 @@ sidebar: order: 0 --- -Bub 有四个概念。**轮次**把一条消息送过整个应用。**tape** 存对话。**事件**报告循环做了什么。**扩展点**是新行为该放的地方。你自己的 Agent 和 Bub 应用都用这四个。 +Bub 有四个概念。**turn** 把一条消息送过整个应用。**tape** 存对话。**事件**报告循环做了什么。**扩展点**是新行为该放的地方。你自己的 Agent 和 Bub 应用都用这四个。 顺序随意。每一页先给结论,再展开。 @@ -21,16 +21,16 @@ Bub 有四个概念。**轮次**把一条消息送过整个应用。**tape** 存 | 概念 | 是什么 | 读哪页 | | --- | --- | --- | -| 轮次 | 一条进来的消息,以及它引起的全部工作。 | [轮次](/zh-cn/docs/concepts/turn/) | +| Turn | 一条进来的消息,以及它引起的全部工作。 | [Turn](/zh-cn/docs/concepts/turn/) | | Tape | 存下来的对话,以及模型实际读到的那一段。 | [Tape](/zh-cn/docs/concepts/tape/) | | 事件 | 报告每一步的总线,处理器可以借它改掉下一步。 | [事件](/zh-cn/docs/concepts/events/) | | 扩展点 | 新行为放的地方:工具、技能、插件或渠道。 | [扩展点](/zh-cn/docs/concepts/extension-points/) | ## 术语 -**Agent** 跑模型与工具的循环。一次**运行**(run)就是一次 `agent.run()` 调用,里面可以有好几个**步骤**(step)。一个**步骤**是一次模型调用,以及它要求的工具。一个**轮次**(turn)是一条进来的消息,和回答它的那次运行。 +**Agent** 跑模型与工具的循环。一次**运行**(run)就是一次 `agent.run()` 调用,里面可以有好几个**步骤**(step)。一个**步骤**是一次模型调用,以及它要求的工具。一个 **turn** 是一条进来的消息,和回答它的那次运行。 -**tape** 存对话。它的**条目**是消息、工具调用、结果和事件。**anchor** 标出上下文的起点。**handoff** 会写一个新的 anchor。本轮的 **state** 是一个独立字典,工具和处理器共用它。 +**tape** 存对话。它的**条目**是消息、工具调用、结果和事件。**anchor** 标出上下文的起点。**handoff** 会写一个新的 anchor。turn 的 **state** 是一个独立字典,工具和处理器共用它。 应用另外加三样东西。**插件**提供 Python hooks。**技能** 写指令。**渠道**收发消息。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/tape.mdx b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx index bd19363a6..d02fb2257 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/tape.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx @@ -73,7 +73,7 @@ async def start_next_phase() -> None: ) ``` -这里的 `state` 存在 anchor 上,位置在默认查询的起点之前。要把摘要带进下一阶段,就把它放进下一次 prompt,或者在你的上下文策略里显式选它。要把值装回本轮的可变状态,得自己写加载逻辑。新会话会自动得到一个 `session/start` anchor。 +这里的 `state` 存在 anchor 上,位置在默认查询的起点之前。要把摘要带进下一阶段,就把它放进下一次提示词,或者在你的上下文策略里显式选它。要把值装回本轮的可变状态,得自己写加载逻辑。新会话会自动得到一个 `session/start` anchor。 想换一个 anchor,就给 `Agent` 传 `context=TapeContext(...)`。也可以用自己的 `select(entries, context)` 函数。选择器返回 Republic 消息,或者返回它们的 awaitable。`default_tape_context()` 含工具往返;不带 `select` 的 `TapeContext` 用更简单的消息转换。带 `context=False` 的条目,在默认或自定义选择器运行之前就被剔除。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx index 68896e222..0aa85e30d 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx @@ -1,13 +1,13 @@ --- -title: 轮次 +title: Turn description: 一条进来的消息,从找会话到把回复发出去。 sidebar: order: 2 --- -一个轮次就是一条进来的消息,以及它引起的全部工作。`BubFramework` 负责一个轮次:找会话、装载状态、构建 prompt、跑模型、发出回复。每个阶段都留了 hook,插件可以改这一段。 +一个 turn 就是一条进来的消息,以及它引起的全部工作。`BubFramework` 负责一个 turn:找会话、装载状态、构建提示词、跑模型、发出回复。每个阶段都留了 hook,插件可以改这一段。 -轮次属于 `bub[app]`。直接调 `core.Agent.run()` 没有轮次:循环就用你给的 prompt、state 和 tape 开始。 +turn 属于 `bub[app]`。直接调 `core.Agent.run()` 没有 turn:循环就用你给的提示词、state 和 tape 开始。 ## 阶段 @@ -16,8 +16,8 @@ sidebar: | 阶段 | 做什么 | | --- | --- | | `resolve_session` | 为收到的消息选出会话 ID。 | -| `load_state` | 从各个 hook 收集本轮状态。 | -| `build_prompt` | 把收到的内容转成 prompt。 | +| `load_state` | 从各个 hook 收集这个 turn 的状态。 | +| `build_prompt` | 把收到的内容转成提示词。 | | `run_model` / `run_model_stream` | 产出模型输出:完整结果,或者一个流。 | | `save_state` | 收尾模型阶段的工作。它在自己的 `finally` 块里运行。 | | `render_outbound` | 把结果转成出站 envelope。 | diff --git a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx index d803fba4d..35c7fe521 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx @@ -40,7 +40,7 @@ Bub 分两层。 几条约束让循环保持简单: -- 历史只追加。每一轮写进 tape。模型读的是这份记录的一个切片,而不是一个无限增长的 chat 数组。 +- 历史只追加。每次 turn 写进 tape。模型读的是这份记录的一个切片,而不是一个无限增长的 chat 数组。 - 每一步都发事件。模型调用和工具调用各自发出开始、结束和结果。宿主可以旁观一次运行,改掉下一次的输入,或者把它停下来。 - 工具就是函数。带类型标注的 Python 函数就够了。code mode 让模型写一小段程序,一步里调用多个工具。 @@ -61,7 +61,7 @@ Bub 分两层。 其余文档,按阅读顺序: -- **[核心概念](/zh-cn/docs/concepts/)**:tape、轮次、事件、扩展点。指南里遇到看不懂的词再回来读,后面的页面不依赖它。 +- **[核心概念](/zh-cn/docs/concepts/)**:tape、turn、事件、扩展点。指南里遇到看不懂的词再回来读,后面的页面不依赖它。 - **[使用 Bub 应用](/zh-cn/docs/app/workspace/)**:先配置,再加工具、技能、插件、hooks 和渠道。 - **[构建你的 Bub Agent](/zh-cn/docs/sdk/)**:留下内核,循环周边自己写。 - **[集成](/zh-cn/docs/integrations/)**:MCP、ACP、agent plugins、本地模型、SQLite、可观测性。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx b/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx index 6cfaafc5d..fa82de92b 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx @@ -33,7 +33,7 @@ bub chat > Summarize the files in this directory ``` -目录里有 `AGENTS.md` 时 Bub 会读取它,把这一轮记到 tape 上,再用你配置的模型回答。按 `Ctrl+C` 退出。 +目录里有 `AGENTS.md` 时 Bub 会读取它,把这次 turn 记到 tape 上,再用你配置的模型回答。按 `Ctrl+C` 退出。 下一步:[工作区](/zh-cn/docs/app/workspace/)讲 `AGENTS.md` 和应用的命令。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/acp.mdx b/website/src/content/docs/zh-cn/docs/integrations/acp.mdx index e2ba83764..744cea8ae 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/acp.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/acp.mdx @@ -43,7 +43,7 @@ Usage: bub acp [OPTIONS] ## 2. 先确认 Bub 本身可用 -在编辑器将要使用的同一个 shell 或环境中运行一个轮次: +在编辑器将要使用的同一个 shell 或环境中运行一个 turn: ```bash bub run "Reply with one short sentence: hello from Bub." @@ -109,11 +109,11 @@ exec bub acp HELLO ``` -你应该会在 Agent 面板中看到 Bub 的回复。工具调用和流式模型文本会通过 ACP 会话更新转发给编辑器,因此轮次运行时可以显示活动状态。 +你应该会在 Agent 面板中看到 Bub 的回复。工具调用和流式模型文本会通过 ACP 会话更新转发给编辑器,因此 turn 运行时可以显示活动状态。 ![Zed Agent 面板中连接为 ACP 外部 Agent 的 Bub](/docs/acp-server/zed-session-content.png) -当编辑器重新打开已有线程时,`bub-acp-server` 会加载匹配的 Bub Tape,并通过和实时轮次相同的 ACP 流式输出路径重放历史。这样恢复出来的用户消息、助手文本和工具事件会与 Bub 记录会话的方式保持一致。 +当编辑器重新打开已有线程时,`bub-acp-server` 会加载匹配的 Bub Tape,并通过和实时 turn 相同的 ACP 流式输出路径重放历史。这样恢复出来的用户消息、助手文本和工具事件会与 Bub 记录会话的方式保持一致。 ## 排障 @@ -124,10 +124,10 @@ HELLO | Bub 已启动但提示配置错误 | 在同一个环境中确认 `bub run` 可用。如果必要设置放在 `.env`,在 `bub acp` 之前加载这份文件。 | | 编辑器无法加载或恢复会话 | 确认编辑器启动的是同一个 `bub` 环境,且其中 `bub acp --help` 可用;同时保持每次启动使用稳定的 `BUB_HOME`。 | | 重启编辑器后旧线程消失 | 保持每次启动使用同一个 Bub 环境和 `BUB_HOME`。`bub-acp-server` 会把 ACP 会话元数据保存在 Bub 数据目录下,并从 Bub Tape 加载历史。 | -| 轮次已开始但界面没有活动显示 | 在 Zed 中运行 `dev: open acp logs`,检查 Zed 与 Bub 之间的 JSON-RPC 消息。 | +| turn 已开始但界面没有活动显示 | 在 Zed 中运行 `dev: open acp logs`,检查 Zed 与 Bub 之间的 JSON-RPC 消息。 | ## 下一步 - [配置](/zh-cn/docs/app/configure/):Bub 的模型和运行时设置。 -- [轮次](/zh-cn/docs/concepts/turn/):Bub 如何把入站消息转成模型调用和出站消息。 +- [Turn](/zh-cn/docs/concepts/turn/):Bub 如何把入站消息转成模型调用和出站消息。 - [构建插件](/zh-cn/docs/app/plugins/):用 hooks、工具和渠道扩展 Bub。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx b/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx index 152c3921d..dd95667a6 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/local-models.mdx @@ -83,13 +83,13 @@ export BUB_API_KEY="$LLAMA_API_KEY" export BUB_MODEL="openai:gemma-4-E2B-it" ``` -运行一个 Bub 轮次: +运行一个 Bub turn: ```bash bub run "Reply with one short sentence: hello from a local model." ``` -现在 Bub 会通过本地 OpenAI 兼容端点进行模型调用。应用轮次流程、渠道、工具和 Tape 都不需要改变。 +现在 Bub 会通过本地 OpenAI 兼容端点进行模型调用。应用 turn 流程、渠道、工具和 Tape 都不需要改变。 ```bash ~/bubbuild/bub$ bub run "Reply with one short sentence: hello from a local model." diff --git a/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx index d70031af5..5262897d2 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx @@ -1,6 +1,6 @@ --- title: 接入 MCP 服务器 -description: 安装 bub-mcp 插件,注册一个时间 MCP 服务器,并在 Bub 轮次中调用其工具。 +description: 安装 bub-mcp 插件,注册一个时间 MCP 服务器,并在 Bub turn 中调用其工具。 sidebar: badge: { text: '应用', variant: 'note' } --- @@ -15,7 +15,7 @@ sidebar: - 已[安装 Bub 应用](/zh-cn/docs/getting-started/install/#app-install),并确认 `bub --help` 可用。 - 系统 `PATH` 中存在 [`uv`](https://docs.astral.sh/uv/),以便 `uvx mcp-server-time` 能按需启动 [time MCP 服务器](https://pypi.org/project/mcp-server-time/)。 -- 如果想在真实轮次中调用 MCP 工具,需要一个可用的模型提供商(参见最后一节)。 +- 如果想在真实 turn 中调用 MCP 工具,需要一个可用的模型提供商(参见最后一节)。 ## 1. 安装 bub-mcp @@ -92,7 +92,7 @@ uvx mcp-server-time ## 4. 在运行中的 turn 里使用 MCP 工具 -`bub mcp list` 已经足以确认接入正确。**真正在轮次中调用工具,需要 Bub 的渠道运行时**: +`bub mcp list` 已经足以确认接入正确。**真正在 turn 中调用工具,需要 Bub 的渠道运行时**: - `bub chat` 会启动 `cli` 交互接口,并自动附着已启用的 `Lifecycle` 后台服务,包括 `mcp.lifecycle`。 - `bub gateway` 会启动 `provide_channels` 钩子返回的所有渠道(受 `--enable-channel` / `BUB_ENABLED_CHANNELS` 控制)。当 `mcp.lifecycle` 启用后,渠道会在后台启动、把每个远程工具按 `mcp._` 名称注册到全局工具表中,模型从此即可调用它们。 @@ -177,7 +177,7 @@ bub mcp remove time | `bub hooks` 中没有 `mcp` | 插件没有装进 `bub` 使用的环境。请使用同一个 `bub` 可执行文件重新安装。 | | `bub mcp list` 显示 `Status: Disconnected` | 在 Bub 之外直接运行配置的 `command`(或访问 `url`)确认能正常启动;错误信息会显示根本原因。 | | `bub mcp add` 在 `Added MCP server …` 之后打印 `CancelledError` | 条目可能已经写入。用 `bub mcp list` 复核,或改为手动编辑 `mcp.json`。 | -| 轮次中工具未被调用 | 确认 `bub mcp list` 显示 `Status: Connected` 且列出了预期工具,然后提一个明显需要该工具的问题。 | +| turn 中工具未被调用 | 确认 `bub mcp list` 显示 `Status: Connected` 且列出了预期工具,然后提一个明显需要该工具的问题。 | | `mcp.json` 权限被拒绝 | 确认 `~/.bub/` 对当前用户可写,或将 `BUB_HOME` 设到你拥有的目录后重试。 | ## 下一步 diff --git a/website/src/content/docs/zh-cn/docs/integrations/observability.mdx b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx index 93e37230e..e298a42c3 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/observability.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx @@ -24,7 +24,7 @@ description: 检查会话记录,并把模型与工具调用轨迹导出到 Pho bub run "What tools do you have, and what small tasks are they useful for?" ``` -然后让 Bub 检查刚刚被这个轮次更新过的 Tape: +然后让 Bub 检查刚刚被这个 turn 更新过的 Tape: ```bash bub run ",tape.info" @@ -133,7 +133,7 @@ bub run ",tape.info" 这条路径与 Tape 检查互补: - Tape 回答“这个 Bub 会话记住了什么?” -- Phoenix 回答“这个 Bub Agent 轮次如何经过模型调用、工具调用和 Tape 更新?” +- Phoenix 回答“这个 Bub Agent turn 如何经过模型调用、工具调用和 Tape 更新?” 排查生产行为时建议两者一起使用:先用 `,tape.info` 判断会话状态,再用 Phoenix 查看耗时、错误、模型调用和工具调用。 diff --git a/website/src/content/docs/zh-cn/docs/reference/cli.mdx b/website/src/content/docs/zh-cn/docs/reference/cli.mdx index 3459e303e..cfb56c2b8 100644 --- a/website/src/content/docs/zh-cn/docs/reference/cli.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/cli.mdx @@ -41,7 +41,7 @@ bub run [OPTIONS] MESSAGE 行为说明: -- 在该次轮次周围打开 `framework.running()`:`provide_tape_store` 在调用前进入,调用后退出。 +- 在一次 turn 期间打开 `framework.running()`:`provide_tape_store` 在调用前进入,调用后退出。 - 出站消息以 `[channel:chat_id]\n` 的格式打印。 - **不** 启用流式输出;模型钩子通过 `HookRuntime.run_model` 调用。 diff --git a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx index e7545e924..b93c2b51d 100644 --- a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx @@ -3,11 +3,11 @@ title: Hooks description: Bub 插件的 hook 签名、调用规则和资源生命周期。 --- -Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 个 hooks。用法示例见[Hooks](/zh-cn/docs/app/hooks/),应用阶段顺序见[轮次](/zh-cn/docs/concepts/turn/)。 +Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 个 hooks。用法示例见[Hooks](/zh-cn/docs/app/hooks/),应用阶段顺序见[Turn](/zh-cn/docs/concepts/turn/)。 下文的“首个”指按 hook 优先级取得第一个非 `None` 的结果,“全部”指调用每个实现。普通 hook 抛出异常时,本次分发停止,异常向上传播;[agent 拦截 hooks](#agent-loop-hooks-pluginagenthooks) 和 `on_error` 有各自的异常处理规则。 -## 轮次 Hooks +## Turn Hooks | Hook | 签名 | 行为 | | --- | --- | --- | diff --git a/website/src/content/docs/zh-cn/docs/reference/python-api.mdx b/website/src/content/docs/zh-cn/docs/reference/python-api.mdx index ae6ab056f..658e779fb 100644 --- a/website/src/content/docs/zh-cn/docs/reference/python-api.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/python-api.mdx @@ -56,7 +56,7 @@ from bub.hooks.interception import ( 应用、插件与渠道使用的接口。 -### 消息与轮次结果 +### 消息与 turn 结果 从 `bub.envelope` 导入消息辅助函数: @@ -137,13 +137,13 @@ from bub.builtin.agent import BubAgent | `BubFramework()` | 加载应用配置,确定工作区 | | `framework.load_hooks()` | 注册内置和已安装插件的 hooks | | `framework.running()` | 获取并释放应用资源 | -| `framework.process_inbound(inbound, stream_output=False)` | 完成应用轮次,返回 `TurnResult` | +| `framework.process_inbound(inbound, stream_output=False)` | 完成应用的 turn,返回 `TurnResult` | | `BubAgent(framework, tools=None, tape_store=None, skill_dirs=None)` | 为核心 agent 接入应用行为 | | `agent.run_stream(session_id=..., prompt=..., state=None, ...)` | 返回应用 agent 的输出流 | 应用配置是进程级共享的。需要使用 framework 当前存储时,在 `framework.running()` 内创建 `BubAgent`。显式提供的 store 优先,生命周期仍由调用方管理。两种 store 都没有时,agent 使用内存存储。 -省略 `state` 时,`run_stream` 加载初始状态;传入 `state` 时,则直接使用调用方提供的状态。状态保存 hooks、出站消息处理和并发轮次调度由宿主管理。完整用法见[嵌入应用](/zh-cn/docs/app/distribution/#programmatic-embedding)。 +省略 `state` 时,`run_stream` 加载初始状态;传入 `state` 时,则直接使用调用方提供的状态。状态保存 hooks、出站消息处理和并发 turn 调度由宿主管理。完整用法见[嵌入应用](/zh-cn/docs/app/distribution/#programmatic-embedding)。 ## 导入路径 diff --git a/website/src/content/docs/zh-cn/docs/reference/settings.mdx b/website/src/content/docs/zh-cn/docs/reference/settings.mdx index 9bf933ec7..fc7cd4cc6 100644 --- a/website/src/content/docs/zh-cn/docs/reference/settings.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/settings.mdx @@ -65,7 +65,7 @@ class AgentSettings(Settings): | `BUB__API_KEY` | 未设置 | 不适用 | 按提供商划分的 API 密钥,例如 `BUB_OPENAI_API_KEY`。 | | `BUB__API_BASE` | 未设置 | 不适用 | 按提供商划分的 API 基础 URL,例如 `BUB_OPENROUTER_API_BASE`。 | | `BUB_PROVIDERS` | `{}` | `providers` | 自定义命名的端点,每个用 `type` 指定走哪个 Republic 提供商的协议,并有自己的 `api_base` 和 `api_key`。见[自定义提供商](#自定义-provider)。 | -| `BUB_MAX_STEPS` | 不限制 | `max_steps` | 单次轮次内 Agent 循环的最大步数;配置时必须为正整数。 | +| `BUB_MAX_STEPS` | 不限制 | `max_steps` | 单次 turn 内 Agent 循环的最大步数;配置时必须为正整数。 | | `BUB_MAX_TOKENS` | `16384` | `max_tokens` | 单次模型调用的最大 token 数;Codex 端点不传此参数。 | | `BUB_MODEL_TIMEOUT_SECONDS` | `null` | `model_timeout_seconds` | 单次调用的超时秒数。 | | `BUB_CLIENT_ARGS` | `{}` | `client_args` | 传递给底层模型客户端的额外关键字参数(JSON / 字典)。 | diff --git a/website/src/content/docs/zh-cn/docs/sdk/index.md b/website/src/content/docs/zh-cn/docs/sdk/index.md index 9ee77c675..fd53f847d 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/index.md +++ b/website/src/content/docs/zh-cn/docs/sdk/index.md @@ -70,9 +70,9 @@ uv run python sdk_example.py ## 管理运行与输出流 -`agent.run()` 返回一个流,迭代这个流会推进执行:调用模型、执行模型请求的工具,再次调用模型,直到模型回答且不再请求工具。一个 `final` 事件表示一个模型步骤结束;完整消费流,才能完成这一轮运行。 +`agent.run()` 返回一个流,迭代这个流会推进执行:调用模型、执行模型请求的工具,再次调用模型,直到模型回答且不再请求工具。一个 `final` 事件表示一个模型步骤结束;完整消费流,才能完成这次运行。 -用 `async with` 管理流。无论正常结束、抛出异常,还是提前 `break`,退出代码块都会关闭流、释放打开的模型流,并合并本轮运行的 Tape 分支。运行失败也会保留已经写入的记录,详见[分支与部分结果](/zh-cn/docs/concepts/tape/#forks)。 +用 `async with` 管理流。无论正常结束、抛出异常,还是提前 `break`,退出代码块都会关闭流、释放打开的模型流,并合并本次运行的 Tape 分支。运行失败也会保留已经写入的记录,详见[分支与部分结果](/zh-cn/docs/concepts/tape/#forks)。 如果另一个组件负责消费输出,可以用参数相同的 `await agent.start(...)` 取得流。接收它的组件必须负责关闭,包括尚未开始迭代的情况。自己消费输出时,优先使用 `run()`。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/options.mdx b/website/src/content/docs/zh-cn/docs/sdk/options.mdx index 300489981..ee9d6a23d 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/options.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/options.mdx @@ -54,7 +54,7 @@ def customer(context: PromptContext) -> str | None: return None ``` -`PromptContext` 包含本轮运行的 prompt、提供给模型的工具、模型标识、Tape、本轮状态和 Agent。`available_tools` 列出 Agent 注册的工具。使用 `agent.system_prompt.remove(section)` 可以移除片段。 +`PromptContext` 包含当前运行的 prompt、提供给模型的工具、模型标识、Tape、turn 状态和 Agent。`available_tools` 列出 Agent 注册的工具。使用 `agent.system_prompt.remove(section)` 可以移除片段。 @@ -75,7 +75,7 @@ def customer(context: PromptContext) -> str | None: | `system_prompt` | 空。每轮运行渲染一次的文本或函数片段。 | | `context` | `default_tape_context()`。选择哪些 Tape 记录进入模型消息。 | | `sidecars` | 空。与 Tape 一起挂载的附加存储能力。 | -| `max_steps` | `sys.maxsize`。一轮运行的模型步骤上限。 | +| `max_steps` | `sys.maxsize`。一次运行的模型步骤上限。 | | `max_tool_concurrency` | `None`,同一次模型响应中的工具调用可以全部并发。设为 `1` 时依次执行。不同运行之间的并发由应用协调。 | | `name` | `"agent"`。Agent trace span 使用的名称。 | | `command_prefix` | `None`。设置前缀后,可以通过文本命令直接调用工具。 | @@ -92,8 +92,8 @@ def customer(context: PromptContext) -> str | None: | 事件 | 发生时机 | 支持的操作 | | --- | --- | --- | -| `MessagesPending` | 每个步骤构建模型请求时,以及步骤结束且没有调用工具时。 | 向 `event.messages` 追加内容;它们在下一个 prompt 之前发送,并一起记录。步骤结束时提供的消息会让运行继续。 | -| `StepFinished` | 步骤结束。`status == "continue"` 时运行会再走一步。 | 设置 `next_prompt` 为下一步添加用户消息;为 `None` 时直接继续对话。`event.prompt` 是本步骤的 prompt,`event.stream` 保存本次运行的用量。 | +| `MessagesPending` | 每个步骤构建模型请求时,以及步骤结束且没有调用工具时。 | 向 `event.messages` 追加内容;它们在下一个提示词之前发送,并一起记录。步骤结束时提供的消息会让运行继续。 | +| `StepFinished` | 步骤结束。`status == "continue"` 时运行会再走一步。 | 设置 `next_prompt` 为下一步添加用户消息;为 `None` 时直接继续对话。`event.prompt` 是本步骤的提示词,`event.stream` 保存本次运行的用量。 | ```python from bub.core.events import MessagesPending, StepFinished diff --git a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx index 54df4a190..aff72ccb9 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx @@ -67,7 +67,7 @@ order_tool = Tool.from_callable( ## 访问当前会话 -传入 `context=True`,工具就会通过关键字参数 `context` 接收 `ToolContext`。其中包含当前 Tape、本轮状态、run ID 和事件总线。 +传入 `context=True`,工具就会通过关键字参数 `context` 接收 `ToolContext`。其中包含当前 Tape、turn 状态、run ID 和事件总线。 ```python from bub.core.tools import ToolContext @@ -112,11 +112,11 @@ print(order["status"]) 在 `run_code` 中,工具名称使用下划线,调用使用关键字参数,结果保留结构化形式。代码支持顶层 `await`。工具调用经过 `ToolCallStarted` 和 `ToolCallFinished`,其中 `context.code_mode=True`。 -`CodeMode` 使用你传入的工具,并排除 `exposure="command"`。BubAgent 通过[应用代码模式设置](/zh-cn/docs/app/tools/)对 `auto`、`direct`、`code` 工具分组。 +`CodeMode` 使用你传入的工具,并排除 `exposure="command"`。BubAgent 通过[应用的 code mode 设置](/zh-cn/docs/app/tools/)对 `auto`、`direct`、`code` 工具分组。 | 选项 | 行为 | | --- | --- | -| `tools` | 工具集合,或根据本轮状态返回工具集合的函数。返回 `None` 可以禁止本轮执行代码。 | +| `tools` | 工具集合,或根据 turn 状态返回工具集合的函数。返回 `None` 可以禁止本轮执行代码。 | | `environment` | 代码运行的位置。默认是在当前目录运行的 `LocalEnvironment()`,也可以是根据状态选择环境的函数。 | | `models` | 代码调用 Republic 时使用的默认 `provider:model`,或返回模型标识与客户端选项的解析函数。不设置时,代码需要给出模型标识,Republic 从环境读取凭据。 | | `stub_dir` | 保存生成的 Python 工具定义的目录。默认直接把定义放入系统提示词;写入文件时,Agent 需要有读取该文件的能力。 | diff --git a/website/src/data/landing-page.ts b/website/src/data/landing-page.ts index fff228516..c7a0096ae 100644 --- a/website/src/data/landing-page.ts +++ b/website/src/data/landing-page.ts @@ -233,7 +233,7 @@ const landingPageData: Record = { icon: 'webhook', title: '按需组合', description: - '内核精简,内置实现均可替换。每个轮次阶段都是一个 pluggy hook,可通过 Python 插件覆盖,无需 fork 运行时。', + '内核精简,内置实现均可替换。每个 turn 阶段都是一个 pluggy hook,可通过 Python 插件覆盖,无需 fork 运行时。', }, { icon: 'layers', From 2f40e53ac318fa2540d7b4b7214eba6de5003829 Mon Sep 17 00:00:00 2001 From: 100gle Date: Wed, 21 Oct 2026 18:30:00 +0800 Subject: [PATCH 10/28] docs: drop legacy URL redirects after the 0.6 restructure The restructure shipped the new tree together with a full 301 map for the old documentation URLs. Treat every old path as gone instead: the site is a single 0.x project with no obligation to keep pre-0.6 links alive. Point the remaining absolute links at the pages they describe so neither README nor the blog relies on a redirect: reuse-plugins, app/workspace, and app/hooks. --- README.md | 4 +- website/astro.config.mjs | 91 ------------------- .../src/content/posts/en/why-rewrite-bub.md | 2 +- .../content/posts/zh-cn/why-rewrite-bub.md | 2 +- 4 files changed, 4 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 3534c4ad6..ff1a40eff 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ uv add bub Run your first agent with the [SDK guide](https://bub.build/docs/sdk/), then [serve it with FastAPI](https://bub.build/docs/sdk/web-service/) when you need an HTTP endpoint. -You can also embed `BubAgent` to reuse the application's settings and plugin hooks in your service. This uses `bub[app]`; [Reuse application plugins](https://bub.build/docs/sdk/app-plugins/) explains the integration choices and their requirements. +You can also embed `BubAgent` to reuse the application's settings and plugin hooks in your service. This uses `bub[app]`; [Reuse application plugins](https://bub.build/docs/sdk/reuse-plugins/) explains the integration choices and their requirements. ## Customize the Bub app @@ -51,7 +51,7 @@ Follow the prompts to choose plugins and configure your model, then start a conv bub chat ``` -[Run your first conversation](https://bub.build/docs/app/first-session/) walks through a task in your workspace. [Customize the Bub app](https://bub.build/docs/app/) covers tools, plugins, and packaging. For `uv tool install` and other setup options, see [Install Bub](https://bub.build/docs/getting-started/install/#app-install). +[Run your first conversation](https://bub.build/docs/app/workspace/) walks through a task in your workspace. [Customize the Bub app](https://bub.build/docs/app/workspace/) covers tools, plugins, and packaging. For `uv tool install` and other setup options, see [Install Bub](https://bub.build/docs/getting-started/install/#app-install). ## Documentation diff --git a/website/astro.config.mjs b/website/astro.config.mjs index a20d74e55..1dc9fe868 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -27,97 +27,6 @@ export default defineConfig({ prerenderEnvironment: 'node', }), site: process.env.SITE_URL ?? 'https://bub.build', - // Documentation moved in the 0.6 documentation restructure. Keep old URLs working. - redirects: { - '/docs': '/docs/getting-started/', - '/docs/app': '/docs/app/workspace/', - '/docs/app/chat': '/docs/app/terminal/', - '/docs/app/first-session': '/docs/app/workspace/', - '/docs/app/gateway': '/docs/app/channels/', - '/docs/app/language-extensions': '/docs/app/plugins/', - '/docs/app/package': '/docs/app/distribution/', - '/docs/app/telegram': '/docs/app/channels/', - '/docs/build': '/docs/app/workspace/', - '/docs/build/app-tools': '/docs/app/tools/', - '/docs/build/distribution': '/docs/app/distribution/', - '/docs/build/hooks': '/docs/app/hooks/', - '/docs/build/plugins': '/docs/app/plugins/', - '/docs/build/reuse-plugins': '/docs/sdk/reuse-plugins/', - '/docs/build/sdk': '/docs/sdk/', - '/docs/build/skills': '/docs/app/skills/', - '/docs/build/tools': '/docs/sdk/tools/', - '/docs/concepts/design': '/docs/concepts/', - '/docs/concepts/philosophy': '/docs/concepts/', - '/docs/concepts/surfaces': '/docs/concepts/extension-points/', - '/docs/concepts/tape-and-context': '/docs/concepts/tape/', - '/docs/concepts/turn-pipeline': '/docs/concepts/turn/', - '/docs/getting-started/first-plugin': '/docs/app/plugins/', - '/docs/getting-started/first-skill': '/docs/app/skills/', - '/docs/getting-started/first-turn': '/docs/app/workspace/', - '/docs/operate': '/docs/app/workspace/', - '/docs/operate/channels': '/docs/app/channels/', - '/docs/operate/channels/cli': '/docs/app/terminal/', - '/docs/operate/channels/telegram': '/docs/app/channels/', - '/docs/operate/configure': '/docs/app/configure/', - '/docs/operate/deploy': '/docs/app/deploy/', - '/docs/sdk/app-plugins': '/docs/sdk/reuse-plugins/', - '/docs/tutorials': '/docs/integrations/', - '/docs/tutorials/acp-server': '/docs/integrations/acp/', - '/docs/tutorials/agent-plugins': '/docs/integrations/agent-plugins/', - '/docs/tutorials/fastapi': '/docs/sdk/web-service/', - '/docs/tutorials/language-extensions': '/docs/app/plugins/', - '/docs/tutorials/local-llama-cpp': '/docs/integrations/local-models/', - '/docs/tutorials/mcp': '/docs/integrations/mcp/', - '/docs/tutorials/observability': '/docs/integrations/observability/', - '/docs/tutorials/tapestore-sqlalchemy': '/docs/integrations/storage/', - '/zh-cn/docs': '/zh-cn/docs/getting-started/', - '/zh-cn/docs/app': '/zh-cn/docs/app/workspace/', - '/zh-cn/docs/app/chat': '/zh-cn/docs/app/terminal/', - '/zh-cn/docs/app/first-session': '/zh-cn/docs/app/workspace/', - '/zh-cn/docs/app/gateway': '/zh-cn/docs/app/channels/', - '/zh-cn/docs/app/language-extensions': '/zh-cn/docs/app/plugins/', - '/zh-cn/docs/app/package': '/zh-cn/docs/app/distribution/', - '/zh-cn/docs/app/telegram': '/zh-cn/docs/app/channels/', - '/zh-cn/docs/build': '/zh-cn/docs/app/workspace/', - '/zh-cn/docs/build/app-tools': '/zh-cn/docs/app/tools/', - '/zh-cn/docs/build/distribution': '/zh-cn/docs/app/distribution/', - '/zh-cn/docs/build/hooks': '/zh-cn/docs/app/hooks/', - '/zh-cn/docs/build/plugins': '/zh-cn/docs/app/plugins/', - '/zh-cn/docs/build/reuse-plugins': '/zh-cn/docs/sdk/reuse-plugins/', - '/zh-cn/docs/build/sdk': '/zh-cn/docs/sdk/', - '/zh-cn/docs/build/skills': '/zh-cn/docs/app/skills/', - '/zh-cn/docs/build/tools': '/zh-cn/docs/sdk/tools/', - '/zh-cn/docs/concepts/design': '/zh-cn/docs/concepts/', - '/zh-cn/docs/concepts/philosophy': '/zh-cn/docs/concepts/', - '/zh-cn/docs/concepts/surfaces': '/zh-cn/docs/concepts/extension-points/', - '/zh-cn/docs/concepts/tape-and-context': '/zh-cn/docs/concepts/tape/', - '/zh-cn/docs/concepts/turn-pipeline': '/zh-cn/docs/concepts/turn/', - '/zh-cn/docs/getting-started/first-plugin': '/zh-cn/docs/app/plugins/', - '/zh-cn/docs/getting-started/first-skill': '/zh-cn/docs/app/skills/', - '/zh-cn/docs/getting-started/first-turn': '/zh-cn/docs/app/workspace/', - '/zh-cn/docs/operate': '/zh-cn/docs/app/workspace/', - '/zh-cn/docs/operate/channels': '/zh-cn/docs/app/channels/', - '/zh-cn/docs/operate/channels/cli': '/zh-cn/docs/app/terminal/', - '/zh-cn/docs/operate/channels/telegram': '/zh-cn/docs/app/channels/', - '/zh-cn/docs/operate/configure': '/zh-cn/docs/app/configure/', - '/zh-cn/docs/operate/deploy': '/zh-cn/docs/app/deploy/', - '/zh-cn/docs/sdk/app-plugins': '/zh-cn/docs/sdk/reuse-plugins/', - '/zh-cn/docs/tutorials': '/zh-cn/docs/integrations/', - '/zh-cn/docs/tutorials/acp-server': '/zh-cn/docs/integrations/acp/', - '/zh-cn/docs/tutorials/agent-plugins': '/zh-cn/docs/integrations/agent-plugins/', - '/zh-cn/docs/tutorials/fastapi': '/zh-cn/docs/sdk/web-service/', - '/zh-cn/docs/tutorials/language-extensions': '/zh-cn/docs/app/plugins/', - '/zh-cn/docs/tutorials/local-llama-cpp': '/zh-cn/docs/integrations/local-models/', - '/zh-cn/docs/tutorials/mcp': '/zh-cn/docs/integrations/mcp/', - '/zh-cn/docs/tutorials/observability': '/zh-cn/docs/integrations/observability/', - '/zh-cn/docs/tutorials/tapestore-sqlalchemy': '/zh-cn/docs/integrations/storage/', - '/docs/reference/types': '/docs/reference/python-api/', - '/docs/reference': '/docs/reference/cli/', - '/docs/reference/versions': '/docs/migration/v0-6/', - '/zh-cn/docs/reference/types': '/zh-cn/docs/reference/python-api/', - '/zh-cn/docs/reference': '/zh-cn/docs/reference/cli/', - '/zh-cn/docs/reference/versions': '/zh-cn/docs/migration/v0-6/', - }, env: { schema: { SITE_URL: envField.string({ diff --git a/website/src/content/posts/en/why-rewrite-bub.md b/website/src/content/posts/en/why-rewrite-bub.md index 70cdcf64f..cf655befc 100644 --- a/website/src/content/posts/en/why-rewrite-bub.md +++ b/website/src/content/posts/en/why-rewrite-bub.md @@ -62,7 +62,7 @@ So what extension points does Bub provide today? Here are a few of the main ones 4. `provide_tape_store()`: allows plugins to define custom tape storage, whether in a database or in an external service. This makes it a natural extension point for memory-system experimentation. Example: [bub-tapestore-sqlite](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-tapestore-sqlite) 5. `provide_channels()`: allows plugins to provide one or more channels. These channels are started when the application starts and torn down when it exits. That makes them useful not only for messaging, but also for any long-running service, such as an HTTP server. [Bub's scheduling system](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-schedule), for example, is built on this interface even though it does not sound like a “channel” at first glance. -For the full plugin API, see the [Bub documentation](https://bub.build/docs/build/hooks/). +For the full plugin API, see the [Bub documentation](https://bub.build/docs/app/hooks/). [^4]: A turn is one complete pass of handling a user prompt, including the entire ReAct loop. diff --git a/website/src/content/posts/zh-cn/why-rewrite-bub.md b/website/src/content/posts/zh-cn/why-rewrite-bub.md index d82979182..187e90a02 100644 --- a/website/src/content/posts/zh-cn/why-rewrite-bub.md +++ b/website/src/content/posts/zh-cn/why-rewrite-bub.md @@ -48,7 +48,7 @@ tags: 4. `provide_tape_store()`,这个接口允许插件自定义 tape 的存储方式,可以保存在 DB 里,或者保存在一个外挂的服务中,非常适合用来改造记忆系统。例子:[bub-tapestore-sqlite](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-tapestore-sqlite) 5. `provide_channels()`,这个接口允许插件提供一个或多个渠道,这个渠道在整个应用周期的开始时启动,结束时销毁,所以不仅可以用来做消息收发的通道,也适合任何需要长时间运行的服务,比如 HTTP server,[bub 的定时任务系统](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-schedule)就是基于这个来实现的,尽管它听上去和「渠道」没什么联系。 -完整的插件接口参考 [bub 的文档](https://bub.build/docs/build/hooks/)。 +完整的插件接口参考 [bub 的文档](https://bub.build/docs/app/hooks/)。 [^4]: 一个 turn 指的是 Agent 处理一次 user prompt 的完整过程,包括一整个 ReAct loop 的执行。 From 08087717e533cb7bc57eed7c1b9d2c610265cc09 Mon Sep 17 00:00:00 2001 From: 100gle Date: Wed, 21 Oct 2026 18:36:36 +0800 Subject: [PATCH 11/28] docs: drop sidebar.order from pages listed in the sidebar config The sidebar is written out explicitly in `astro.config.mjs`, and Starlight only consults `sidebar.order` while sorting an autogenerated directory (`sortDirEntries` is reached from `groupFromDir`). Explicit `items` keep the array order, so the field has been dead weight on 46 pages. Keep the six frontmatter blocks that still carry a sidebar badge. --- website/src/content/docs/docs/app/channels.mdx | 2 -- website/src/content/docs/docs/app/configure.mdx | 2 -- website/src/content/docs/docs/app/deploy.mdx | 2 -- website/src/content/docs/docs/app/distribution.mdx | 2 -- website/src/content/docs/docs/app/hooks.mdx | 2 -- website/src/content/docs/docs/app/plugins.mdx | 2 -- website/src/content/docs/docs/app/skills.mdx | 2 -- website/src/content/docs/docs/app/terminal.mdx | 2 -- website/src/content/docs/docs/app/tools.mdx | 2 -- website/src/content/docs/docs/app/workspace.mdx | 2 -- website/src/content/docs/docs/concepts/events.mdx | 2 -- website/src/content/docs/docs/concepts/extension-points.mdx | 2 -- website/src/content/docs/docs/concepts/index.mdx | 2 -- website/src/content/docs/docs/concepts/tape.mdx | 2 -- website/src/content/docs/docs/concepts/turn.mdx | 2 -- website/src/content/docs/docs/getting-started/index.mdx | 2 -- website/src/content/docs/docs/getting-started/install.mdx | 2 -- website/src/content/docs/docs/getting-started/quickstart.mdx | 2 -- website/src/content/docs/docs/sdk/index.md | 2 -- website/src/content/docs/docs/sdk/options.mdx | 2 -- website/src/content/docs/docs/sdk/reuse-plugins.mdx | 2 -- website/src/content/docs/docs/sdk/tools.mdx | 2 -- website/src/content/docs/docs/sdk/web-service.mdx | 2 -- website/src/content/docs/zh-cn/docs/app/channels.mdx | 2 -- website/src/content/docs/zh-cn/docs/app/configure.mdx | 2 -- website/src/content/docs/zh-cn/docs/app/deploy.mdx | 2 -- website/src/content/docs/zh-cn/docs/app/distribution.mdx | 2 -- website/src/content/docs/zh-cn/docs/app/hooks.mdx | 2 -- website/src/content/docs/zh-cn/docs/app/plugins.mdx | 2 -- website/src/content/docs/zh-cn/docs/app/skills.mdx | 2 -- website/src/content/docs/zh-cn/docs/app/terminal.mdx | 2 -- website/src/content/docs/zh-cn/docs/app/tools.mdx | 2 -- website/src/content/docs/zh-cn/docs/app/workspace.mdx | 2 -- website/src/content/docs/zh-cn/docs/concepts/events.mdx | 2 -- .../src/content/docs/zh-cn/docs/concepts/extension-points.mdx | 2 -- website/src/content/docs/zh-cn/docs/concepts/index.mdx | 2 -- website/src/content/docs/zh-cn/docs/concepts/tape.mdx | 2 -- website/src/content/docs/zh-cn/docs/concepts/turn.mdx | 2 -- website/src/content/docs/zh-cn/docs/getting-started/index.mdx | 2 -- website/src/content/docs/zh-cn/docs/getting-started/install.mdx | 2 -- .../src/content/docs/zh-cn/docs/getting-started/quickstart.mdx | 2 -- website/src/content/docs/zh-cn/docs/sdk/index.md | 2 -- website/src/content/docs/zh-cn/docs/sdk/options.mdx | 2 -- website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx | 2 -- website/src/content/docs/zh-cn/docs/sdk/tools.mdx | 2 -- website/src/content/docs/zh-cn/docs/sdk/web-service.mdx | 2 -- 46 files changed, 92 deletions(-) diff --git a/website/src/content/docs/docs/app/channels.mdx b/website/src/content/docs/docs/app/channels.mdx index 114b299d6..06ffada8d 100644 --- a/website/src/content/docs/docs/app/channels.mdx +++ b/website/src/content/docs/docs/app/channels.mdx @@ -1,8 +1,6 @@ --- title: Channels description: Choose the channels the gateway starts, tune batching and session ids, and configure the Telegram bot. -sidebar: - order: 3 --- A channel is how messages enter and leave the Bub app. `cli` is the terminal, `telegram` is a bot on Telegram, and a plugin can add another messaging platform or a background runtime. The gateway starts the enabled channels, batches their messages, and keys their sessions. The application framework manages channels. A service that uses an Agent directly reads its own input and writes its own output. diff --git a/website/src/content/docs/docs/app/configure.mdx b/website/src/content/docs/docs/app/configure.mdx index 912b25e49..7720c4ba1 100644 --- a/website/src/content/docs/docs/app/configure.mdx +++ b/website/src/content/docs/docs/app/configure.mdx @@ -1,8 +1,6 @@ --- title: Configuration description: Set the Bub application's model, provider credentials, and channels. -sidebar: - order: 1 --- Choose the model the Bub application calls and the channels it connects to. These settings belong to the application. When you construct `core.Agent` directly, pass its model and components in Python; see [Build your own agent](/docs/sdk/). diff --git a/website/src/content/docs/docs/app/deploy.mdx b/website/src/content/docs/docs/app/deploy.mdx index 7faaffec6..b24890fbc 100644 --- a/website/src/content/docs/docs/app/deploy.mdx +++ b/website/src/content/docs/docs/app/deploy.mdx @@ -1,8 +1,6 @@ --- title: Deployment description: Run the Bub application with Docker Compose and keep its data across restarts. -sidebar: - order: 10 --- Run the Bub application in a container, mount its data on the host, and choose the gateway channels it starts. Download and unpack the source archive for Bub 0.6.0 from [GitHub Releases](https://github.com/bubbuild/bub/releases), then run the commands below from that directory. diff --git a/website/src/content/docs/docs/app/distribution.mdx b/website/src/content/docs/docs/app/distribution.mdx index 99d497a6d..860dc7fb4 100644 --- a/website/src/content/docs/docs/app/distribution.mdx +++ b/website/src/content/docs/docs/app/distribution.mdx @@ -1,8 +1,6 @@ --- title: Distribution description: Embed the application framework or ship a CLI with your own plugins and skills. -sidebar: - order: 11 --- Build on the Bub app when you want its configuration, plugins, and message routing in your product. You can host `BubFramework` in an existing process or ship a command that starts it. diff --git a/website/src/content/docs/docs/app/hooks.mdx b/website/src/content/docs/docs/app/hooks.mdx index 80a61e767..07e7c999b 100644 --- a/website/src/content/docs/docs/app/hooks.mdx +++ b/website/src/content/docs/docs/app/hooks.mdx @@ -1,8 +1,6 @@ --- title: Hooks description: Use hooks to shape prompts, control calls, save application state, and provide resources. -sidebar: - order: 8 --- Choose a hook at the point where you want to change the Bub app. Keep the rest of the application in place: a prompt hook changes the input, a tool decision controls one call, and a store provider changes where conversation history lives. diff --git a/website/src/content/docs/docs/app/plugins.mdx b/website/src/content/docs/docs/app/plugins.mdx index 1544d977d..f9e9ff57b 100644 --- a/website/src/content/docs/docs/app/plugins.mdx +++ b/website/src/content/docs/docs/app/plugins.mdx @@ -1,8 +1,6 @@ --- title: Plugins description: Write a plugin, choose its entry point, and install it into the app. -sidebar: - order: 7 --- A plugin changes how the Bub app handles a task. This page builds a small echo plugin so you can check discovery and turn processing without model credentials, then covers the entry-point choices, tool registration, sharing, and the WebAssembly route for other languages. diff --git a/website/src/content/docs/docs/app/skills.mdx b/website/src/content/docs/docs/app/skills.mdx index 3b14ce9f2..7edfa0e11 100644 --- a/website/src/content/docs/docs/app/skills.mdx +++ b/website/src/content/docs/docs/app/skills.mdx @@ -1,8 +1,6 @@ --- title: Skills description: Write a reusable procedure, check that the app loads it, and package it for other users. -sidebar: - order: 6 --- A skill gives the Bub app a procedure it can load for a task. This page walks through a project skill, then covers the file fields, discovery order, and packaging. Keep project-specific skills in the workspace, or include them in a Python package so other users can install them. diff --git a/website/src/content/docs/docs/app/terminal.mdx b/website/src/content/docs/docs/app/terminal.mdx index 1a021c705..764221f00 100644 --- a/website/src/content/docs/docs/app/terminal.mdx +++ b/website/src/content/docs/docs/app/terminal.mdx @@ -1,8 +1,6 @@ --- title: Terminal description: Continue a conversation with bub chat or run one task from the shell with bub run. -sidebar: - order: 2 --- The Bub app has two terminal entry points: `bub chat` for a conversation and `bub run` for one message. [Configure a model](/docs/app/configure/) before calling it. diff --git a/website/src/content/docs/docs/app/tools.mdx b/website/src/content/docs/docs/app/tools.mdx index df23c88f8..b904d0813 100644 --- a/website/src/content/docs/docs/app/tools.mdx +++ b/website/src/content/docs/docs/app/tools.mdx @@ -1,8 +1,6 @@ --- title: Tools description: Register tools for BubAgent and choose how the app makes them available. -sidebar: - order: 5 --- Give the Bub app an action it can perform for your work. The [Tools and CodeMode](/docs/sdk/tools/) page explains function schemas, results, and tool context. This page covers application registration and the policies applied by `BubAgent`. diff --git a/website/src/content/docs/docs/app/workspace.mdx b/website/src/content/docs/docs/app/workspace.mdx index 10999ac10..73fa87460 100644 --- a/website/src/content/docs/docs/app/workspace.mdx +++ b/website/src/content/docs/docs/app/workspace.mdx @@ -1,8 +1,6 @@ --- title: Workspace description: Create a workspace, add your instructions, and check that commands work. -sidebar: - order: 0 --- Bub works in a directory: the workspace. It reads and writes files there, and it takes the project instructions from there. diff --git a/website/src/content/docs/docs/concepts/events.mdx b/website/src/content/docs/docs/concepts/events.mdx index 710e4ee0c..64cf162af 100644 --- a/website/src/content/docs/docs/concepts/events.mdx +++ b/website/src/content/docs/docs/concepts/events.mdx @@ -1,8 +1,6 @@ --- title: Events description: Watch a run, change or stop a model or tool call, and record the events that you need. -sidebar: - order: 4 --- Agent events let you watch a run. They also let you decide before a model or tool call. Every Agent creates an `EventBus` and subscribes a `TapeRecorder`. You add handlers through `agent.events`. diff --git a/website/src/content/docs/docs/concepts/extension-points.mdx b/website/src/content/docs/docs/concepts/extension-points.mdx index a21c7e13d..3dd621e4b 100644 --- a/website/src/content/docs/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/docs/concepts/extension-points.mdx @@ -1,8 +1,6 @@ --- title: Extension points description: Decide whether your task needs a tool, an event handler, a skill, a plugin, or a channel. -sidebar: - order: 5 --- Start with the behavior that you want to add. A new action needs a tool. A new place to receive messages needs a channel. You can combine these capabilities, but do not give them the same job. diff --git a/website/src/content/docs/docs/concepts/index.mdx b/website/src/content/docs/docs/concepts/index.mdx index aa2c9617d..2d8162413 100644 --- a/website/src/content/docs/docs/concepts/index.mdx +++ b/website/src/content/docs/docs/concepts/index.mdx @@ -1,8 +1,6 @@ --- title: Overview description: The four concepts that both ways of using Bub share, the layer each one lives in, and where to read about it. -sidebar: - order: 0 --- Bub has four concepts. A **turn** carries one message through the app. A **tape** stores the conversation. **Events** report what the loop does. An **extension point** is a place where new behavior goes. Your own Agent and the Bub app use all four. diff --git a/website/src/content/docs/docs/concepts/tape.mdx b/website/src/content/docs/docs/concepts/tape.mdx index 579a5ad49..bd737d187 100644 --- a/website/src/content/docs/docs/concepts/tape.mdx +++ b/website/src/content/docs/docs/concepts/tape.mdx @@ -1,8 +1,6 @@ --- title: Tape description: Where Bub keeps the conversation, what the model reads on the next call, and what survives a run. -sidebar: - order: 3 --- An agent needs two things. It needs a record of its work. It also needs a useful set of messages for the next model call. Bub keeps the record in a tape. It builds the model context from the entries that you select. Because these are separate, you can keep the full history and still send a short request. diff --git a/website/src/content/docs/docs/concepts/turn.mdx b/website/src/content/docs/docs/concepts/turn.mdx index b6bacb488..922ceb5a5 100644 --- a/website/src/content/docs/docs/concepts/turn.mdx +++ b/website/src/content/docs/docs/concepts/turn.mdx @@ -1,8 +1,6 @@ --- title: Turn description: One inbound message, from the session lookup to the reply that leaves the app. -sidebar: - order: 2 --- A turn is one inbound message, and all the work that it causes. `BubFramework` handles a turn: it finds the session, loads the state, builds the prompt, runs the model, and delivers the reply. Each stage has a hook, so a plugin can change that stage. diff --git a/website/src/content/docs/docs/getting-started/index.mdx b/website/src/content/docs/docs/getting-started/index.mdx index 1a0e59d35..25c9fe7fb 100644 --- a/website/src/content/docs/docs/getting-started/index.mdx +++ b/website/src/content/docs/docs/getting-started/index.mdx @@ -1,8 +1,6 @@ --- title: What is Bub? description: An agent runtime written in Python. Run it as an application, or import it in your own program. -sidebar: - order: 0 --- import { CardGrid, LinkCard } from '@astrojs/starlight/components'; diff --git a/website/src/content/docs/docs/getting-started/install.mdx b/website/src/content/docs/docs/getting-started/install.mdx index 49c1a147c..d2b1ed3b8 100644 --- a/website/src/content/docs/docs/getting-started/install.mdx +++ b/website/src/content/docs/docs/getting-started/install.mdx @@ -1,8 +1,6 @@ --- title: Install Bub description: Install Bub 0.6.0 for your own Python agent or the Bub application. -sidebar: - order: 1 --- import { Tabs, TabItem } from '@astrojs/starlight/components'; diff --git a/website/src/content/docs/docs/getting-started/quickstart.mdx b/website/src/content/docs/docs/getting-started/quickstart.mdx index 31febc7c3..5ff978aa2 100644 --- a/website/src/content/docs/docs/getting-started/quickstart.mdx +++ b/website/src/content/docs/docs/getting-started/quickstart.mdx @@ -1,8 +1,6 @@ --- title: Quick start description: Run Bub in your terminal, or embed it in a Python program. -sidebar: - order: 2 --- import { CardGrid, LinkCard, Tabs, TabItem } from '@astrojs/starlight/components'; diff --git a/website/src/content/docs/docs/sdk/index.md b/website/src/content/docs/docs/sdk/index.md index 2aef71ad4..e99d241dc 100644 --- a/website/src/content/docs/docs/sdk/index.md +++ b/website/src/content/docs/docs/sdk/index.md @@ -1,8 +1,6 @@ --- title: Basic agent description: Create a basic agent in your own Python program and control the stream it produces. -sidebar: - order: 0 --- Choose a model, add tools, and connect the agent to your program. `bub.core.Agent` runs the model and tool loop while you decide how to store conversations and manage the surrounding application. diff --git a/website/src/content/docs/docs/sdk/options.mdx b/website/src/content/docs/docs/sdk/options.mdx index 11ed823e7..d0f8ad0c2 100644 --- a/website/src/content/docs/docs/sdk/options.mdx +++ b/website/src/content/docs/docs/sdk/options.mdx @@ -1,8 +1,6 @@ --- title: Configure the agent description: Choose the model, the instructions, and the other options that shape a run. -sidebar: - order: 1 --- These options shape how an agent runs. Start from the [basic agent](/docs/sdk/) example, then change the options you need. diff --git a/website/src/content/docs/docs/sdk/reuse-plugins.mdx b/website/src/content/docs/docs/sdk/reuse-plugins.mdx index a0cb6f53c..292dad433 100644 --- a/website/src/content/docs/docs/sdk/reuse-plugins.mdx +++ b/website/src/content/docs/docs/sdk/reuse-plugins.mdx @@ -1,8 +1,6 @@ --- title: Reusing plugins description: Bring plugin tools and hooks into your own agent while keeping your existing service and calling code. -sidebar: - order: 4 --- Use Bub's application plugins in your existing Python service. Your host keeps its HTTP routes, task handlers, and output handling. Choose the agent and runtime according to the application behavior you want to reuse. diff --git a/website/src/content/docs/docs/sdk/tools.mdx b/website/src/content/docs/docs/sdk/tools.mdx index 91f787304..29067fb0b 100644 --- a/website/src/content/docs/docs/sdk/tools.mdx +++ b/website/src/content/docs/docs/sdk/tools.mdx @@ -1,8 +1,6 @@ --- title: Tools and CodeMode description: Define an action once, then give it to an Agent directly or compose it with CodeMode. -sidebar: - order: 3 --- A tool gives your agent an action: look up an order, read a file, or call a service. `Tool` describes the action's name, parameters, and result. You can pass it directly to an Agent or register it with the [Bub application](/docs/app/tools/). diff --git a/website/src/content/docs/docs/sdk/web-service.mdx b/website/src/content/docs/docs/sdk/web-service.mdx index eab9eee71..c042fd049 100644 --- a/website/src/content/docs/docs/sdk/web-service.mdx +++ b/website/src/content/docs/docs/sdk/web-service.mdx @@ -1,8 +1,6 @@ --- title: "Example: FastAPI service" description: Give your Agent an HTTP endpoint and keep a separate conversation for each session. -sidebar: - order: 5 --- Give your Agent an HTTP endpoint so another program can send it work. Each request names a session; requests with the same name continue a conversation stored on disk. diff --git a/website/src/content/docs/zh-cn/docs/app/channels.mdx b/website/src/content/docs/zh-cn/docs/app/channels.mdx index 7d94a2b0c..501e36c0c 100644 --- a/website/src/content/docs/zh-cn/docs/app/channels.mdx +++ b/website/src/content/docs/zh-cn/docs/app/channels.mdx @@ -1,8 +1,6 @@ --- title: 渠道 description: 选择网关启动哪些渠道,设置消息合并与会话 ID,并配置 Telegram 机器人。 -sidebar: - order: 3 --- 渠道是消息进出 Bub 应用的方式。`cli` 是终端,`telegram` 是 Telegram 上的机器人,插件还能再加一个聊天平台或后台运行时。网关负责启动已启用的渠道、合并它们的消息、给它们的会话命名。渠道由应用框架管理。直接用 Agent 的服务,自己读输入,自己写输出。 diff --git a/website/src/content/docs/zh-cn/docs/app/configure.mdx b/website/src/content/docs/zh-cn/docs/app/configure.mdx index 03268d7c3..cbbb531ad 100644 --- a/website/src/content/docs/zh-cn/docs/app/configure.mdx +++ b/website/src/content/docs/zh-cn/docs/app/configure.mdx @@ -1,8 +1,6 @@ --- title: 配置 description: 配置 Bub 应用的模型、提供商凭据和渠道。 -sidebar: - order: 1 --- 选择 Bub 应用调用的模型和接入的渠道。这些配置属于应用;直接构造 `core.Agent` 时,在 Python 中传入模型与组件,参见[构建自己的 Agent](/zh-cn/docs/sdk/)。 diff --git a/website/src/content/docs/zh-cn/docs/app/deploy.mdx b/website/src/content/docs/zh-cn/docs/app/deploy.mdx index c3eb5fa50..e5a92e7c7 100644 --- a/website/src/content/docs/zh-cn/docs/app/deploy.mdx +++ b/website/src/content/docs/zh-cn/docs/app/deploy.mdx @@ -1,8 +1,6 @@ --- title: 部署 description: 用 Docker Compose 运行 Bub 应用,并在重启后保留数据。 -sidebar: - order: 10 --- 把 Bub 应用放进容器,将数据挂载到宿主机,并选择网关启动的渠道。从 [GitHub Releases](https://github.com/bubbuild/bub/releases) 获取 Bub 0.6.0 的源码归档并解压,在解压后的目录中运行以下命令。 diff --git a/website/src/content/docs/zh-cn/docs/app/distribution.mdx b/website/src/content/docs/zh-cn/docs/app/distribution.mdx index 91b009cf8..097b50347 100644 --- a/website/src/content/docs/zh-cn/docs/app/distribution.mdx +++ b/website/src/content/docs/zh-cn/docs/app/distribution.mdx @@ -1,8 +1,6 @@ --- title: 分发 description: 嵌入应用框架,或发布包含自有插件和技能的 CLI。 -sidebar: - order: 11 --- 如果产品需要 Bub 的配置、插件和消息路由,可以在 Bub 应用上继续构建。你可以把 `BubFramework` 放进现有进程,也可以发布一个启动它的命令。 diff --git a/website/src/content/docs/zh-cn/docs/app/hooks.mdx b/website/src/content/docs/zh-cn/docs/app/hooks.mdx index bd64d2129..dc556edc7 100644 --- a/website/src/content/docs/zh-cn/docs/app/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/app/hooks.mdx @@ -1,8 +1,6 @@ --- title: Hooks description: 通过 hooks 调整提示词、控制调用、保存应用状态并提供资源。 -sidebar: - order: 8 --- 在你想改变的位置选一个 hook(钩子),其余部分继续用 Bub 应用的实现。提示词 hook 改变输入,工具 decision 控制一次调用,存储 provider 决定会话历史保存在哪里。 diff --git a/website/src/content/docs/zh-cn/docs/app/plugins.mdx b/website/src/content/docs/zh-cn/docs/app/plugins.mdx index 76fedee91..93bc18c8b 100644 --- a/website/src/content/docs/zh-cn/docs/app/plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/app/plugins.mdx @@ -1,8 +1,6 @@ --- title: 插件 description: 编写插件、选择入口点形式,并安装到应用里。 -sidebar: - order: 7 --- 插件可以改变 Bub 应用处理任务的方式。本页先带你写一个回声插件,无需模型凭据就能检查插件发现和 turn 处理,再说明入口点的几种形式、工具注册、分享方式,以及用其他语言实现的办法。 diff --git a/website/src/content/docs/zh-cn/docs/app/skills.mdx b/website/src/content/docs/zh-cn/docs/app/skills.mdx index 05caa2646..b2b7abf8a 100644 --- a/website/src/content/docs/zh-cn/docs/app/skills.mdx +++ b/website/src/content/docs/zh-cn/docs/app/skills.mdx @@ -1,8 +1,6 @@ --- title: 技能 description: 编写可复用的流程,确认应用能加载它,并打包给其他用户使用。 -sidebar: - order: 6 --- 技能是一份写好的流程,让 Bub 应用按步骤完成任务。本页先带你写一个项目技能,再说明文件字段、发现顺序和打包方式。项目专用的技能可以留在工作区,也可以随 Python 包分发,让别人装上就能用。 diff --git a/website/src/content/docs/zh-cn/docs/app/terminal.mdx b/website/src/content/docs/zh-cn/docs/app/terminal.mdx index 37304dcc2..6c9252db2 100644 --- a/website/src/content/docs/zh-cn/docs/app/terminal.mdx +++ b/website/src/content/docs/zh-cn/docs/app/terminal.mdx @@ -1,8 +1,6 @@ --- title: 终端 description: 用 bub chat 继续对话,或用 bub run 从 shell 执行一个任务。 -sidebar: - order: 2 --- Bub 应用提供两种终端用法:`bub chat` 用于交互式对话,`bub run` 用于执行一条消息。先[配置模型](/zh-cn/docs/app/configure/)。 diff --git a/website/src/content/docs/zh-cn/docs/app/tools.mdx b/website/src/content/docs/zh-cn/docs/app/tools.mdx index 7dbd134b7..7a623cf45 100644 --- a/website/src/content/docs/zh-cn/docs/app/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/app/tools.mdx @@ -1,8 +1,6 @@ --- title: 工具 description: 为 BubAgent 注册工具,并选择应用如何向模型提供它们。 -sidebar: - order: 5 --- 给 Bub 应用添加工作中需要执行的动作。[工具与 CodeMode](/zh-cn/docs/sdk/tools/)说明函数参数、结果和工具上下文。本页介绍应用注册,以及 `BubAgent` 执行的工具策略。 diff --git a/website/src/content/docs/zh-cn/docs/app/workspace.mdx b/website/src/content/docs/zh-cn/docs/app/workspace.mdx index 8f455737b..0deea7724 100644 --- a/website/src/content/docs/zh-cn/docs/app/workspace.mdx +++ b/website/src/content/docs/zh-cn/docs/app/workspace.mdx @@ -1,8 +1,6 @@ --- title: 工作区 description: 建一个工作区,放进去你的指令,再确认命令能用。 -sidebar: - order: 0 --- Bub 在一个目录里工作,这个目录就是工作区。它在这里读写文件,也从这里取项目的指令。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/events.mdx b/website/src/content/docs/zh-cn/docs/concepts/events.mdx index 90e7db64b..7a4d1681d 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/events.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/events.mdx @@ -1,8 +1,6 @@ --- title: 事件 description: 看一次运行,改掉或叫停模型与工具调用,并记录你需要的事件。 -sidebar: - order: 4 --- Agent 事件能让你看到一次运行,也能让你在模型或工具调用之前做决定。每个 Agent 都会建一个 `EventBus`,并挂上一个 `TapeRecorder`。你通过 `agent.events` 添加处理器。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx index 908aa869c..74d0a82a6 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx @@ -1,8 +1,6 @@ --- title: 扩展点 description: 判断你的任务需要工具、事件处理器、技能、插件,还是渠道。 -sidebar: - order: 5 --- 先想清楚你要加什么行为。加一个新动作,用工具。加一个新的收消息的地方,用渠道。这些能力可以叠着用,但别让它们干同一件事。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/index.mdx b/website/src/content/docs/zh-cn/docs/concepts/index.mdx index ceb70b74c..c09453e54 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/index.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/index.mdx @@ -1,8 +1,6 @@ --- title: 概览 description: 两种用法共用的四个概念,各自在哪一层,以及去哪一页读。 -sidebar: - order: 0 --- Bub 有四个概念。**turn** 把一条消息送过整个应用。**tape** 存对话。**事件**报告循环做了什么。**扩展点**是新行为该放的地方。你自己的 Agent 和 Bub 应用都用这四个。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/tape.mdx b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx index d02fb2257..7836cf0d9 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/tape.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx @@ -1,8 +1,6 @@ --- title: Tape description: 对话存在哪里,下一次调用模型读到什么,以及一次运行结束后还能剩下什么。 -sidebar: - order: 3 --- Agent 需要两样东西。一是自己干过什么的记录,二是下一次模型调用要用的一组消息。Bub 把记录存进 tape,再用你选中的条目拼出模型上下文。两者分开,你就能留下完整历史,同时只发一个短请求。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx index 0aa85e30d..30167ea78 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx @@ -1,8 +1,6 @@ --- title: Turn description: 一条进来的消息,从找会话到把回复发出去。 -sidebar: - order: 2 --- 一个 turn 就是一条进来的消息,以及它引起的全部工作。`BubFramework` 负责一个 turn:找会话、装载状态、构建提示词、跑模型、发出回复。每个阶段都留了 hook,插件可以改这一段。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx index 35c7fe521..92c6853c1 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx @@ -1,8 +1,6 @@ --- title: Bub 是什么? description: 一个用 Python 写的 agent 运行时。可以作为应用运行,也可以引入到你自己的程序里。 -sidebar: - order: 0 --- import { CardGrid, LinkCard } from '@astrojs/starlight/components'; diff --git a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx index 99e962c89..9e597eab3 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx @@ -1,8 +1,6 @@ --- title: 安装 Bub description: 安装 Bub 0.6.0,用于组合自己的 Python Agent 或运行 Bub 应用。 -sidebar: - order: 1 --- import { Tabs, TabItem } from '@astrojs/starlight/components'; diff --git a/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx b/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx index fa82de92b..85900f1fb 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx @@ -1,8 +1,6 @@ --- title: 快速上手 description: 在终端里运行 Bub,或把它嵌进 Python 程序。 -sidebar: - order: 2 --- import { CardGrid, LinkCard, Tabs, TabItem } from '@astrojs/starlight/components'; diff --git a/website/src/content/docs/zh-cn/docs/sdk/index.md b/website/src/content/docs/zh-cn/docs/sdk/index.md index fd53f847d..602538913 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/index.md +++ b/website/src/content/docs/zh-cn/docs/sdk/index.md @@ -1,8 +1,6 @@ --- title: 基础 Agent description: 在一个最小的 Python 程序里跑起 Agent,并控制它产生的输出流。 -sidebar: - order: 0 --- 选择模型,加入工具,把 Agent 接到你的程序里。`bub.core.Agent` 负责模型与工具之间的循环;你决定如何保存会话,以及它怎样与你的系统协作。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/options.mdx b/website/src/content/docs/zh-cn/docs/sdk/options.mdx index ee9d6a23d..ae857b1dc 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/options.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/options.mdx @@ -1,8 +1,6 @@ --- title: 配置 Agent description: 选择模型、指令,以及影响一次运行的其他选项。 -sidebar: - order: 1 --- 这些选项决定 Agent 怎么跑。先在[基础 Agent](/zh-cn/docs/sdk/)的示例上跑通,再按需修改。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx b/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx index 32b87f0a7..259274837 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx @@ -1,8 +1,6 @@ --- title: 复用插件 description: 将插件提供的工具和 hooks 接入自己的 Agent,保留现有服务入口与调用方式。 -sidebar: - order: 4 --- 你可以在现有 Python 服务中使用 Bub 应用插件。宿主继续管理 HTTP 路由、任务入口和输出处理;根据要复用的应用行为,选择 Agent 和运行入口。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx index aff72ccb9..8521651b2 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx @@ -1,8 +1,6 @@ --- title: 工具与 CodeMode description: 定义一个动作,再直接交给 Agent,或通过 CodeMode 从 Python 调用。 -sidebar: - order: 3 --- 工具让 Agent 执行动作:查询订单、读取文件,或调用一个服务。`Tool` 描述动作的名称、参数和结果。你可以把它直接传给 Agent,也可以在 [Bub 应用](/zh-cn/docs/app/tools/)中注册。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/web-service.mdx b/website/src/content/docs/zh-cn/docs/sdk/web-service.mdx index f05a35b3d..80a294dbc 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/web-service.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/web-service.mdx @@ -1,8 +1,6 @@ --- title: 示例:FastAPI 服务 description: 为 Agent 提供 HTTP 接口,并为每个会话保留独立历史。 -sidebar: - order: 5 --- 为 Agent 提供 HTTP 接口,让其他程序可以向它提交任务。每个请求指定会话名称;使用相同名称的请求会继续磁盘上保存的会话。 From 0691856c20db8c168aeebc7a95d90dfeb4fb0f2c Mon Sep 17 00:00:00 2001 From: 100gle Date: Thu, 22 Oct 2026 15:01:50 +0800 Subject: [PATCH 12/28] docs: rewrite the install guide and the 0.6 migration note Cut the install page to the three paths that exist: CLI, SDK, source. Drop the virtual-environment section and move the PATH hint into an aside. Rewrite the migration note around the kernel/app dependency split, with tabs for the install methods, and mirror it in zh-cn. --- .../docs/docs/getting-started/index.mdx | 69 +++++---------- .../docs/docs/getting-started/install.mdx | 84 ++++--------------- .../src/content/docs/docs/migration/v0-6.mdx | 39 ++++++--- .../docs/zh-cn/docs/getting-started/index.mdx | 71 +++++----------- .../zh-cn/docs/getting-started/install.mdx | 84 ++++--------------- .../docs/zh-cn/docs/migration/v0-6.mdx | 39 ++++++--- 6 files changed, 129 insertions(+), 257 deletions(-) diff --git a/website/src/content/docs/docs/getting-started/index.mdx b/website/src/content/docs/docs/getting-started/index.mdx index 25c9fe7fb..0aa594251 100644 --- a/website/src/content/docs/docs/getting-started/index.mdx +++ b/website/src/content/docs/docs/getting-started/index.mdx @@ -1,66 +1,39 @@ --- title: What is Bub? -description: An agent runtime written in Python. Run it as an application, or import it in your own program. +description: A lightweight agent runtime written in Python. Run it as-is, or embed it in your own program. --- +import { Aside } from '@astrojs/starlight/components'; -import { CardGrid, LinkCard } from '@astrojs/starlight/components'; +Bub is a lightweight agent runtime written in Python. It ships a ready-to-run agent application, and it lets you replace the execution logic and compose plugins as you see fit. -Bub is an agent runtime written in Python. It needs Python 3.12 or newer and installs from PyPI with `pip` or `uv`. +An agent is at heart a loop: the model reads the context and decides which tool to call, the tool result goes back into the context, and the model reads it again. The loop ends when the model stops asking for tools and answers directly. -You give Bub three things: a model, a set of tools, and a place to keep the conversation. Then it repeats one loop. It calls the model, runs the tools the model asks for, writes the results to the conversation, and calls the model again. The loop ends when the model answers without asking for a tool. +So an agent rests on four things: a **model**, **tools**, a **context**, and the **loop** that drives them. Working from that shared understanding, Bub keeps a small core and builds a ready-to-run application around it: -Everything else in Bub exists to make that loop usable outside a demo: settings, storage, channels, plugins, and a way to watch what a run is doing. +- **The core layer**: composable agent capabilities such as the loop, tools, context, the event bus, and the model client. It calls models through [republic](https://getrepublic.org) and depends only on [pydantic](https://pydantic.dev) and [loguru](https://github.com/delgan/loguru). -## What it contains +- **The app layer**: a ready-to-run harness — settings, plugins, hooks, skills, message channels, a gateway, and more. None of it needs to be built yourself; a little configuration is enough to start. -Bub has two layers. +Which one should you use? -**The kernel** (`bub.core`) has the loop, the tool interface, the conversation record, the event bus, and the model client. It uses `republic` for model calls, and depends on `pydantic` and `loguru`. +- If you want to **combine Bub with your existing codebase or service**, use the core layer [SDK](/docs/sdk/) directly; +- If you want **Bub to work out of the box**, use the app layer [App](/docs/app/workspace/). -**The application layer** (`bub[app]`) adds what turns the kernel into a program you can run: settings and config files, plugins and hooks, skills, message channels, a gateway, and the `bub` command. - -`pip install bub` installs the kernel. `pip install "bub[app]"` installs both layers. - -## Two ways to use it - -| | Use Bub App | Build your own Bub Agent | -| --- | --- | --- | -| What runs | the `bub` command, in its own process | your Python program | -| How you start it | `bub chat`, `bub run`, `bub gateway` | `Agent(...)` | -| What you provide | model and channel settings | a model, tools, and a tape | -| What Bub provides | settings, plugins, skills, hooks, channels | the loop and its events | - -Use the app when you want an agent that already works in a terminal or a chat service. Build your own agent when you are adding agent behavior to a service you run yourself. - -The two are not exclusive. A program that imports `BubAgent` gets the kernel plus the app's settings and plugin hooks, without running the `bub` command. + ## Core design -A few rules keep the loop small: - -- History is append-only. Each turn is written to a tape. The model reads a slice of that tape, not a chat array that grows without limit. -- Every step reports an event. Model calls and tool calls each report a start, an end, and a result. A host can watch a run, change the next input, or stop it. -- A tool is a function. A Python function with type hints is enough. Code mode lets the model write a short program that calls several tools in one step. +Bub is built on being simple, composable, and extensible. That means you can freely combine Bub's capabilities, or build your own agent on top of it: -## Where to go next +1. **Context is append-only.** Each turn appends to the tape and never rewrites an entry. The model reads a slice of the tape, not a message array that grows without limit. +2. **Every step reports an event.** Model calls and tool calls each report a start, an end, and a result. A host can watch a run, change the next input, or stop it. +3. **A tool is a function.** A Python function with type hints is enough. Code mode lets the model write a short program that calls several tools in one step. +4. **Everything is a plugin.** Every capability in Bub can come from a plugin. A plugin can be a tool, an event listener, a message channel, or a whole set of skills. - - - - +The [concepts](/docs/concepts/) section defines all of this in full. -The rest of the documentation, in reading order: +--- -- **[Concepts](/docs/concepts/)**: tape, turns, events, extension points. Read these when a term in the guides is unclear; nothing else depends on them. -- **[Use the Bub app](/docs/app/workspace/)**: configure it, then add tools, skills, plugins, hooks, and channels. -- **[Build your own agent](/docs/sdk/)**: keep the kernel and write the loop's surroundings yourself. -- **[Integrations](/docs/integrations/)**: MCP, ACP, agent plugins, local models, SQLite, and tracing. -- **[Reference](/docs/reference/cli/)**: CLI commands, settings, Python API, and hooks. +Now you can explore the Bub docs freely, or follow the next page and start your own journey with Bub. diff --git a/website/src/content/docs/docs/getting-started/install.mdx b/website/src/content/docs/docs/getting-started/install.mdx index d2b1ed3b8..1e86edafe 100644 --- a/website/src/content/docs/docs/getting-started/install.mdx +++ b/website/src/content/docs/docs/getting-started/install.mdx @@ -1,17 +1,11 @@ --- title: Install Bub -description: Install Bub 0.6.0 for your own Python agent or the Bub application. +description: Install Bub step by step, with several install paths to choose from. --- -import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Tabs, TabItem, Aside } from '@astrojs/starlight/components'; -Choose the installation for the way you want to use Bub. Add Bub to a Python project with [uv](https://docs.astral.sh/uv/getting-started/installation/), or install the application with a setup script or uv. - - - -## Run the Bub application - -Choose an installation method: +## Install the CLI app @@ -35,77 +29,29 @@ uv tool install "bub[app]" uv tool update-shell ``` -Open a new terminal, then run `bub onboard` to configure your model and channels. - -The shell and PowerShell scripts set up uv and Python 3.12 as needed, then install `bub[app]` into `~/.bub/.venv`. They prompt you to choose a plugin preset and configure your model and channels. - -When installation finishes, make sure the command directory is on your PATH, then check: - -```bash -bub --help -``` - -Continue with [Workspace](/docs/app/workspace/) to create a workspace and add your project instructions. +The script installs uv and Python 3.12, and puts `bub[app]` into your current environment. -The base install and `bub[app]` contain the same package files. The extra changes which dependencies are installed. Without the application dependencies, the `bub` command exits with an instruction to install `bub[app]`. +When it finishes, run `bub --help`, then run `bub onboard` to start configuring, and confirm the command line works. - + -## Build your own agent +## Install the SDK -Use Python 3.12 or later and add Bub to your project: +From 0.6.0 on, Bub ships the core only. Use your package manager to add Bub to your project as a dependency. Here is uv: ```bash uv add bub uv run python -c "from bub.core import Agent; print(Agent.__name__)" ``` -If you are starting a new project, first run `uv init --python 3.12 my-agent` and `cd my-agent`. - -The base installation includes the kernel's dependencies: Republic, Pydantic, Loguru, and typing-extensions. The import check needs no model credentials. Continue with [Build your own agent](/docs/sdk/) to configure a model in Python and run the first example. - - - -## Use a virtual environment for application development +## Run from source -For plugin development or adding packages such as tracing exporters, use an environment you can activate: - -```bash -uv venv --python 3.12 -source .venv/bin/activate -uv pip install "bub[app]" -bub --help -``` - -In PowerShell, replace the activation command with `.\.venv\Scripts\Activate.ps1`. Run the application and install its extensions from this same environment. If your project already uses `uv`, you can instead add `"bub[app]"` with `uv add` and run its commands with `uv run bub`. - - - -## Declare a package dependency - -Add the dependency to the existing `[project]` table in your package's `pyproject.toml`. For direct Agent use: - -```toml -requires-python = ">=3.12" -dependencies = ["bub>=0.6.0"] -``` - -For a package that imports the builtin agent, Framework, plugins, skills, or channels: - -```toml -dependencies = ["bub[app]>=0.6.0"] -``` - -Synchronize your project after editing its dependencies. Existing applications should follow [Upgrade to 0.6.0](/docs/migration/v0-6/) when updating the dependency. - - - -## Contribute from source - -Clone the repository to work on Bub itself: +Clone the repository and sync its dependencies: ```bash git clone https://github.com/bubbuild/bub.git @@ -113,4 +59,8 @@ cd bub uv sync ``` -`uv sync` installs the development group, including application dependencies and Python check tools. You can then use `uv run bub` and the repository's test commands. +Then run it with `uv run bub`. + +--- + +With that in place, go and give Bub a try. diff --git a/website/src/content/docs/docs/migration/v0-6.mdx b/website/src/content/docs/docs/migration/v0-6.mdx index 907e1c619..952980955 100644 --- a/website/src/content/docs/docs/migration/v0-6.mdx +++ b/website/src/content/docs/docs/migration/v0-6.mdx @@ -1,33 +1,46 @@ --- title: Upgrade to 0.6.0 -description: Update application dependencies for the app extra and choose the Agent interfaces your code needs. +description: Migration notes for the 0.6.0 split of kernel and application dependencies. --- -Bub 0.6.0 separates the Agent kernel's dependencies from the application's dependencies. When upgrading a CLI installation, plugin, or distribution, include the `app` extra. +import { Tabs, TabItem } from '@astrojs/starlight/components'; -## Update an application installation +Before 0.6.0, `bub` covered both the application and the kernel; from 0.6.0 on, installing `bub` gives you the kernel only, and the application moved into the optional `app` dependencies. If you need the Bub application layer (configuration, plugins, hooks, skills, message channels, the gateway, and so on), add `bub[app]` when you install. -For an application installed with `uv tool`: +## Upgrade guide + +Pick the upgrade step for your current install: + + + + +Installed with `uv tool`: ```bash uv tool install --upgrade --force "bub[app]" ``` -For an activated application environment: + + + +In an activated virtual environment: ```bash uv pip install --upgrade "bub[app]" ``` -In a plugin or distribution's `pyproject.toml`, use `bub[app]>=0.6.0` in place of a plain `bub` dependency. The extra installs the dependencies required by the builtin runtime, Framework, settings, plugin hooks, skills, and channels. + + + +For a plugin or a distribution, change the dependency in `pyproject.toml`: -Run `bub --help`, then [run a conversation in the terminal](/docs/app/terminal/). Use `bub hooks` when checking that your plugins loaded. +```toml +dependencies = ["bub[app]>=0.6.0"] +``` -## Choose the interfaces your code uses + + -| Your code | Dependency and guide | -| --- | --- | -| Supplies a model and tools directly to `core.Agent` | `bub`; [Build your own agent](/docs/sdk/) | -| Uses `BubAgent`, the Framework, application hooks, or channels | `bub[app]`; [Use the Bub app](/docs/app/workspace/) | +## Verify the upgrade -A directly composed Agent takes its model and components in Python. The Bub application supplies its settings, skills, and tool policies through `BubAgent` and the Framework. [Tools](/docs/sdk/tools/) and [Agent events](/docs/concepts/events/) explain the shared contracts and the differences in default behavior. +Once the upgrade finishes, go to the [terminal](/docs/app/terminal/) and enter `bub --help`, start a quick conversation, and run `bub hooks` to see which plugins loaded. diff --git a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx index 92c6853c1..8ebed02a5 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx @@ -1,66 +1,39 @@ --- title: Bub 是什么? -description: 一个用 Python 写的 agent 运行时。可以作为应用运行,也可以引入到你自己的程序里。 +description: 一个由 Python 构建的轻量级 Agent 运行时。可以直接运行,也可以嵌入你自己的程序。 --- +import { Aside } from '@astrojs/starlight/components'; -import { CardGrid, LinkCard } from '@astrojs/starlight/components'; +Bub 是一个由 Python 构建的轻量级 Agent 运行时。它自带一个开箱即用的 Agent 应用,也可以按需替换执行逻辑、自由组合插件。 -Bub 是一个用 Python 写的 agent 运行时。它要求 Python 3.12 或更高版本,用 `pip` 或 `uv` 从 PyPI 安装。 +Agent 的本质是一个循环:模型读取上下文并决定调用哪个工具,工具的结果写回上下文,再交给模型;当模型不再请求工具、直接给出答案时,循环结束。 -你给 Bub 三样东西:一个模型、一组工具、一个存放对话的地方。然后它重复同一个循环:调用模型,执行模型要求的工具,把结果写进对话,再调用模型。模型不再要求工具、直接给出回答时,循环结束。 +构成一个 Agent 的核心有四个:**模型**、**工具**、**上下文**,以及驱动它们的**循环**。所以在这样的共识下 Bub 设计了精简的内核,并围绕内核构建出了一个开箱即用的应用: -Bub 其余的部分,都是为了让这个循环在演示之外也能用:配置、存储、渠道、插件,以及看清一次运行在做什么的手段。 +- **核心层**:提供可自由组合的 Agent 能力,如循环、工具、上下文、事件总线、模型客户端等,它通过 [republic](https://getrepublic.org) 调用模型,仅依赖 [pydantic](https://pydantic.dev) 与 [loguru](https://github.com/delgan/loguru)。 -## 它由什么组成 +- **应用层**:提供了开箱即用的 Harness,如配置、插件、hooks、技能、消息渠道、网关等,这些能力都无需你去一一实现,使用时只需简单配置即可开始使用。 -Bub 分两层。 +如何选择? -**内核**(`bub.core`)有循环、工具接口、对话记录、事件总线和模型客户端。它用 `republic` 调用模型,依赖 `pydantic` 和 `loguru`。 +- 如果你想**把 Bub 与你现有的代码库或服务相结合**,那么请直接使用 Bub 核心层的 [SDK](/zh-cn/docs/sdk/); +- 如果你想**开箱即用 Bub**,那么请直接使用 Bub 应用层的 [App](/zh-cn/docs/app/workspace/)。 -**应用层**(`bub[app]`)加上让内核变成一个能直接运行的程序所需的部分:配置和配置文件、插件与 hooks、技能、消息渠道、网关,以及 `bub` 命令。 + -`pip install bub` 只装内核。`pip install "bub[app]"` 两层都装。 +## 核心设计理念 -## 两种用法 +Bub 秉持着简单、可组合、可扩展的设计理念,这意味着你可以自由组合 Bub 的能力,或在 Bub 的基础上构建自己的 Agent: -| | 使用 Bub 应用 | 构建你的 Bub Agent | -| --- | --- | --- | -| 运行的是什么 | `bub` 命令,独占一个进程 | 你自己的 Python 程序 | -| 怎么启动 | `bub chat`、`bub run`、`bub gateway` | `Agent(...)` | -| 你需要提供 | 模型和渠道配置 | 模型、工具、一个 tape | -| Bub 提供 | 配置、插件、技能、hooks、渠道 | 循环和它的事件 | +1. **追加式上下文**:每个 turn 追加写入 tape,不修改已有条目。模型读的是 tape 的一个切片,而不是一个不断增长的 message 数组。 +2. **事件驱动**:模型调用与工具调用各自发出开始、结束和结果。宿主可以旁观一次运行、改写下一步的输入,或直接中止。 +3. **工具即函数**:一个有类型标注的 Python 函数即可成为工具。Code mode 让模型自己写一小段程序,在一步之内调用多个工具。 +4. **插件化**:Bub 的能力都可以通过插件提供。插件可以是一个工具、一个事件监听器、一个消息渠道,或一个完整的技能集合。 -想要一个在终端或聊天软件里就能用的 agent,用应用。想给自己跑的服务加上 agent 行为,就自己构建。 +完整定义见[核心概念](/zh-cn/docs/concepts/)。 -两者并不冲突。导入 `BubAgent` 的程序会拿到内核,外加应用的配置和插件 hooks,但不需要跑 `bub` 命令。 - -## 核心设计 - -几条约束让循环保持简单: - -- 历史只追加。每次 turn 写进 tape。模型读的是这份记录的一个切片,而不是一个无限增长的 chat 数组。 -- 每一步都发事件。模型调用和工具调用各自发出开始、结束和结果。宿主可以旁观一次运行,改掉下一次的输入,或者把它停下来。 -- 工具就是函数。带类型标注的 Python 函数就够了。code mode 让模型写一小段程序,一步里调用多个工具。 - -## 接下来 - - - - - - -其余文档,按阅读顺序: +--- -- **[核心概念](/zh-cn/docs/concepts/)**:tape、turn、事件、扩展点。指南里遇到看不懂的词再回来读,后面的页面不依赖它。 -- **[使用 Bub 应用](/zh-cn/docs/app/workspace/)**:先配置,再加工具、技能、插件、hooks 和渠道。 -- **[构建你的 Bub Agent](/zh-cn/docs/sdk/)**:留下内核,循环周边自己写。 -- **[集成](/zh-cn/docs/integrations/)**:MCP、ACP、agent plugins、本地模型、SQLite、可观测性。 -- **[参考](/zh-cn/docs/reference/cli/)**:CLI 命令、配置项、Python API 和 hooks。 +现在你可以开始自由探索 Bub 文档,或者跟着下一页,开始你的 Bub 之旅。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx index 9e597eab3..0aa19c065 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx @@ -1,17 +1,11 @@ --- title: 安装 Bub -description: 安装 Bub 0.6.0,用于组合自己的 Python Agent 或运行 Bub 应用。 +description: 跟着文档一步步安装 Bub,提供了多种安装途径。 --- -import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Tabs, TabItem, Aside } from '@astrojs/starlight/components'; -按你的使用方式选择安装路径。Python 项目通过 [uv](https://docs.astral.sh/uv/getting-started/installation/) 添加 Bub;运行应用可以使用安装脚本或 uv。 - - - -## 运行 Bub 应用 - -选择适合你的安装方式: +## 安装 CLI 应用 @@ -35,77 +29,29 @@ uv tool install "bub[app]" uv tool update-shell ``` -打开新终端后,运行 `bub onboard` 配置模型和渠道。 - -脚本会按需安装 uv 和 Python 3.12,并将 `bub[app]` 安装到 `~/.bub/.venv`。按提示选择插件预设,再完成模型和渠道配置。 - -安装结束后,确认命令目录已加入 PATH,再检查: - -```bash -bub --help -``` - -接着看[工作区](/zh-cn/docs/app/workspace/),建一个工作区,写下项目指令。 +运行上述脚本会帮你安装 uv 和 Python 3.12,并把 `bub[app]` 到当前环境中。 -基础安装与 `bub[app]` 包含相同的包文件;extra 改变的是安装的依赖。缺少应用依赖时,`bub` 命令会退出,并提示安装 `bub[app]`。 +完成后在命令中执行 `bub --help` 并用 `bub onboard` 开始配置以确认命令行是否能正常工作。 - + -## 构建自己的 Agent +## 安装 SDK -使用 Python 3.12 或更高版本,在项目中添加 Bub: +自 0.6.0 起,Bub 默认只包含核心,可以根据使用对应包管理工具将 Bub 作为依赖安装到项目中。这里以 uv 为例: ```bash uv add bub uv run python -c "from bub.core import Agent; print(Agent.__name__)" ``` -如果还没有项目,先运行 `uv init --python 3.12 my-agent`,再进入 `cd my-agent`。 - -基础安装包含内核所需的 Republic、Pydantic、Loguru 和 typing-extensions。导入检查无需模型凭据。接着阅读[构建自己的 Agent](/zh-cn/docs/sdk/),在 Python 中配置模型并运行第一个示例。 - - - -## 在虚拟环境中开发应用 +## 从源码运行 -开发插件或添加追踪导出器等包时,可以创建并激活一个虚拟环境: - -```bash -uv venv --python 3.12 -source .venv/bin/activate -uv pip install "bub[app]" -bub --help -``` - -PowerShell 中的激活命令为 `.\.venv\Scripts\Activate.ps1`。在同一个环境中运行应用并安装扩展。如果项目已经使用 `uv`,也可以通过 `uv add` 添加 `"bub[app]"`,再用 `uv run bub` 运行命令。 - - - -## 声明包依赖 - -把依赖添加到项目 `pyproject.toml` 已有的 `[project]` 表。直接使用 Agent 的包声明: - -```toml -requires-python = ">=3.12" -dependencies = ["bub>=0.6.0"] -``` - -如果包会导入内置 Agent、Framework、插件、技能或渠道,则声明: - -```toml -dependencies = ["bub[app]>=0.6.0"] -``` - -修改依赖后,同步项目环境。现有应用更新依赖时,参照[升级到 0.6.0](/zh-cn/docs/migration/v0-6/)。 - - - -## 从源码参与开发 - -为 Bub 本身贡献代码时,克隆仓库: +克隆项目仓库,按如下步骤同步依赖即可: ```bash git clone https://github.com/bubbuild/bub.git @@ -113,4 +59,8 @@ cd bub uv sync ``` -`uv sync` 会安装开发依赖组,其中包含应用依赖和 Python 检查工具。之后可以使用 `uv run bub` 和仓库的测试命令。 +之后执行 `uv run bub` 运行即可。 + +--- + +一切就绪之后就可以开始上手试试怎么使用 Bub 吧! \ No newline at end of file diff --git a/website/src/content/docs/zh-cn/docs/migration/v0-6.mdx b/website/src/content/docs/zh-cn/docs/migration/v0-6.mdx index 9d417d2ab..82b070567 100644 --- a/website/src/content/docs/zh-cn/docs/migration/v0-6.mdx +++ b/website/src/content/docs/zh-cn/docs/migration/v0-6.mdx @@ -1,33 +1,46 @@ --- title: 升级到 0.6.0 -description: 为应用依赖添加 app extra,并按代码需要选择 Agent 接口。 +description: 0.6.0 内核与应用依赖分离的迁移说明。 --- -Bub 0.6.0 将 Agent 内核依赖与应用依赖分开。升级 CLI 安装、插件或发行版时,请加入 `app` extra。 +import { Tabs, TabItem } from '@astrojs/starlight/components'; -## 更新应用安装 +0.6.0 之前版本的 `bub` 包含应用和内核两个部分;从 0.6.0 开始,安装 `bub` 时仅包含内核,而应用挪到了 `app` 可选依赖中。如果你需要使用 Bub 应用层的能力(如配置、插件、hooks、技能、消息渠道、网关等),请在安装时加上 `bub[app]`。 -通过 `uv tool` 安装的应用使用: +## 升级指南 + +根据你当前的安装方式,选择下面的升级方法: + + + + +用 `uv tool` 安装的: ```bash uv tool install --upgrade --force "bub[app]" ``` -已经激活的应用虚拟环境使用: + + + +在已经激活的虚拟环境里: ```bash uv pip install --upgrade "bub[app]" ``` -插件或发行版的 `pyproject.toml` 也应把普通 `bub` 依赖改为 `bub[app]>=0.6.0`。这个 extra 安装内置运行时、Framework、配置、插件 hooks、技能和渠道所需的依赖。 + + + +插件或发行版改 `pyproject.toml` 里的依赖: -运行 `bub --help`,再在[终端](/zh-cn/docs/app/terminal/)里跑一轮对话。检查插件是否加载时,使用 `bub hooks`。 +```toml +dependencies = ["bub[app]>=0.6.0"] +``` -## 按代码用途选择接口 + + -| 代码用途 | 依赖与指南 | -| --- | --- | -| 直接向 `core.Agent` 传入模型和工具 | `bub`;[构建自己的 Agent](/zh-cn/docs/sdk/) | -| 使用 `BubAgent`、Framework、应用 hooks 或渠道 | `bub[app]`;[使用 Bub 应用](/zh-cn/docs/app/workspace/) | +## 升级验证 -直接组合的 Agent 在 Python 中接收模型与组件。Bub 应用通过 `BubAgent` 和 Framework 提供配置、技能与工具策略。[工具](/zh-cn/docs/sdk/tools/)和 [Agent 事件](/zh-cn/docs/concepts/events/)说明共同契约与默认行为的区别。 +升级完成后在去[终端](/zh-cn/docs/app/terminal/)命令行中输入 `bub --help`、快速开始一轮对话或再用 `bub hooks` 看插件加载情况。 From d3e29770552b54f5f24a69d519baf4541bc3910b Mon Sep 17 00:00:00 2001 From: 100gle Date: Thu, 22 Oct 2026 15:13:11 +0800 Subject: [PATCH 13/28] docs: remove manual empty anchors --- .../src/content/docs/docs/app/distribution.mdx | 6 +----- website/src/content/docs/docs/app/hooks.mdx | 6 ++---- website/src/content/docs/docs/app/plugins.mdx | 4 +--- website/src/content/docs/docs/app/skills.mdx | 2 -- website/src/content/docs/docs/app/tools.mdx | 16 +++++----------- .../src/content/docs/docs/concepts/events.mdx | 14 ++------------ .../docs/docs/concepts/extension-points.mdx | 2 +- .../src/content/docs/docs/concepts/tape.mdx | 10 ---------- .../content/docs/docs/integrations/index.mdx | 2 +- .../src/content/docs/docs/reference/hooks.mdx | 8 ++------ .../content/docs/docs/reference/python-api.mdx | 6 ++---- website/src/content/docs/docs/sdk/index.md | 4 ---- website/src/content/docs/docs/sdk/options.mdx | 16 ---------------- .../content/docs/docs/sdk/reuse-plugins.mdx | 10 +++------- website/src/content/docs/docs/sdk/tools.mdx | 18 +++--------------- .../content/docs/zh-cn/docs/app/configure.mdx | 2 -- .../docs/zh-cn/docs/app/distribution.mdx | 6 +----- .../src/content/docs/zh-cn/docs/app/hooks.mdx | 8 ++------ .../content/docs/zh-cn/docs/app/plugins.mdx | 4 +--- .../src/content/docs/zh-cn/docs/app/skills.mdx | 2 -- .../src/content/docs/zh-cn/docs/app/tools.mdx | 16 +++++----------- .../docs/zh-cn/docs/concepts/events.mdx | 14 ++------------ .../zh-cn/docs/concepts/extension-points.mdx | 2 +- .../content/docs/zh-cn/docs/concepts/tape.mdx | 10 ---------- .../docs/zh-cn/docs/integrations/index.mdx | 2 +- .../docs/zh-cn/docs/reference/hooks.mdx | 12 ++---------- .../docs/zh-cn/docs/reference/python-api.mdx | 6 ++---- .../src/content/docs/zh-cn/docs/sdk/index.md | 4 ---- .../content/docs/zh-cn/docs/sdk/options.mdx | 16 ---------------- .../docs/zh-cn/docs/sdk/reuse-plugins.mdx | 10 +++------- .../src/content/docs/zh-cn/docs/sdk/tools.mdx | 18 +++--------------- 31 files changed, 46 insertions(+), 210 deletions(-) diff --git a/website/src/content/docs/docs/app/distribution.mdx b/website/src/content/docs/docs/app/distribution.mdx index 860dc7fb4..fc034ca39 100644 --- a/website/src/content/docs/docs/app/distribution.mdx +++ b/website/src/content/docs/docs/app/distribution.mdx @@ -7,8 +7,6 @@ Build on the Bub app when you want its configuration, plugins, and message routi This path uses the application dependencies in [Install Bub](/docs/getting-started/install/). For a program that directly composes an Agent and its components, follow [Build your own agent](/docs/sdk/). - - ## Embed the application Use `BubFramework.process_inbound` to run the application's full turn: resolve the session, load state, prepare the prompt, execute, save state, render, and dispatch. @@ -43,8 +41,6 @@ Keep `framework.running()` open for the service's lifetime. It acquires hook-pro `process_inbound` returns a completed `TurnResult`. With `stream_output=True`, it consumes the stream through the bound channel router before returning that result. The host owns concurrent message scheduling and any per-session locking it requires. - - ## Reuse plugins in your own service A host can use plugin-provided tools, stores, or hooks while keeping its own API and stream handling. Follow [Reusing plugins](/docs/sdk/reuse-plugins/) to connect those capabilities to `core.Agent`. @@ -85,6 +81,6 @@ channels = "my_bub.channels:ChannelPlugin" packages = ["src/my_bub", "src/skills"] ``` -Each target follows the [plugin registration rules](/docs/app/plugins/). Include task instructions using the [skills package layout](/docs/app/skills/#ship-skills-with-your-package). +Each target follows the [plugin registration rules](/docs/app/plugins/). Include task instructions using the [skills package layout](/docs/app/skills/#include-skills-in-a-wheel). Before release, install the built wheel into a separate environment. Run the command, confirm the plugin report, load a packaged skill, and exercise a representative conversation. For a hosted application, also check shutdown while a stream or background tool is active. diff --git a/website/src/content/docs/docs/app/hooks.mdx b/website/src/content/docs/docs/app/hooks.mdx index 07e7c999b..7ce5e889d 100644 --- a/website/src/content/docs/docs/app/hooks.mdx +++ b/website/src/content/docs/docs/app/hooks.mdx @@ -36,8 +36,6 @@ def system_prompt(prompt, state): return "Explain which files support your answer." ``` - - ## Control a model or tool call Use the agent interception hooks when you want to retain the builtin agent and change an individual call. For example, cap a request's output tokens: @@ -70,7 +68,7 @@ def before_tool_call(call: ToolCall, state): Your authorization logic sets `can_cancel_orders`; the hook uses that value to allow or deny the call. -An exception in these interception hooks is logged and skipped. Use `deny`, `replace`, or `LLMCallDecision.finish` for control. The [interception contract](/docs/reference/hooks/#agent-loop-hooks-pluginagenthooks) explains when a decision stops the chain. +An exception in these interception hooks is logged and skipped. Use `deny`, `replace`, or `LLMCallDecision.finish` for control. The [interception contract](/docs/reference/hooks/#interception-through-pluginagenthooks) explains when a decision stops the chain. ## Observe completed calls @@ -90,7 +88,7 @@ def after_tool_call(call, result, state): print(f"tool={call.tool} duration_ms={result.duration_ms} error={result.error!r}") ``` -These hooks observe completed calls, including ordinary failures. They do not report cancellation. For per-chunk output, use the stream interface in your host or channel and follow its [closing rules](/docs/reference/python-api/#streaming). +These hooks observe completed calls, including ordinary failures. They do not report cancellation. For per-chunk output, use the stream interface in your host or channel and follow its [closing rules](/docs/reference/python-api/#streams). To replace the entire model stage, implement either `run_model` or `run_model_stream`. [Your first plugin](/docs/app/plugins/) shows a text replacement. A replacement owns its execution and recording behavior; the builtin agent's interception hooks run only when that agent is used. diff --git a/website/src/content/docs/docs/app/plugins.mdx b/website/src/content/docs/docs/app/plugins.mdx index f9e9ff57b..16269f3b6 100644 --- a/website/src/content/docs/docs/app/plugins.mdx +++ b/website/src/content/docs/docs/app/plugins.mdx @@ -88,7 +88,7 @@ With no other plugin overriding the model stage, the response contains: If `echo` is missing, check that the package was installed in the environment running `bub`, its entry-point group, and startup logs for an import failure. Changes to package metadata require reinstalling the package. If it is listed but another response wins, inspect the other model hooks and their priority. -Bub registers builtin hooks before entry-point plugins. Hooks at equal priority generally run in reverse registration order; `tryfirst` and `trylast` affect that order. The [dispatch rules](/docs/reference/hooks/#iteration-order) explain which implementation runs first. +Bub registers builtin hooks before entry-point plugins. Hooks at equal priority generally run in reverse registration order; `tryfirst` and `trylast` affect that order. The [dispatch rules](/docs/reference/hooks/#priority-and-dispatch) explain which implementation runs first. ## Choose an entry-point target @@ -159,8 +159,6 @@ Build and install a wheel into a separate environment before publishing it. Conf Portable Agent Plugins use a different format: `plugin.json`, skills, and MCP servers. The [Agent Plugins integration](/docs/integrations/agent-plugins/) loads those packages through a Python plugin. - - ## WASM plugins (experimental) A plugin does not have to be Python. The [`bub-extism`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) plugin runs WebAssembly modules and maps their exports to hooks. Bub still dispatches the hooks and decides precedence; Extism only calls your module. diff --git a/website/src/content/docs/docs/app/skills.mdx b/website/src/content/docs/docs/app/skills.mdx index 7edfa0e11..396eadc5a 100644 --- a/website/src/content/docs/docs/app/skills.mdx +++ b/website/src/content/docs/docs/app/skills.mdx @@ -83,8 +83,6 @@ A project skill can therefore replace a packaged skill with the same name. `BubA The system prompt lists available skills. A `$skill-name` hint expands that skill for the current turn; `,skill name=skill-name` prints its body through the command path. - - ## Include skills in a wheel diff --git a/website/src/content/docs/docs/app/tools.mdx b/website/src/content/docs/docs/app/tools.mdx index b904d0813..70792e531 100644 --- a/website/src/content/docs/docs/app/tools.mdx +++ b/website/src/content/docs/docs/app/tools.mdx @@ -42,9 +42,7 @@ Run `bub hooks` to check that your plugin implements `provide_tools`. The model- When calling `BubAgent.run_stream`, pass `allowed_tools` to limit the model's tools for that turn. It accepts runtime names or model-facing names. `None` allows all instance tools; an empty collection allows none. Unknown names raise `ValueError`. -This filter applies to model and code-mode tools. Comma commands use the agent's instance tools directly. Use [tool-call decisions](/docs/app/hooks/#control-calls) when a rule must also govern command execution. - - +This filter applies to model and code-mode tools. Comma commands use the agent's instance tools directly. Use [tool-call decisions](/docs/app/hooks/#control-a-model-or-tool-call) when a rule must also govern command execution. ## Load tools when needed @@ -64,8 +62,6 @@ def get_order(order_id: str) -> dict[str, str]: The turn's allowlist controls both listing and loading. In code mode, allowed `auto` and `code` tools are available to Python immediately; deferral applies to the tools still offered directly to the model. - - ## Use code mode in the app Enable code mode for the current session: @@ -87,17 +83,15 @@ When `run_code` is allowed, the app selects tools according to `exposure`: If `run_code` is excluded, the turn uses the code-mode-off selection. The app writes a Python tool stub under `/codemode/` and includes its path in the system prompt. Python calls such as `await tools.orders_get(order_id="123")` use the selected tools and pass through the application's interception hooks. -The app connects CodeMode to the session's environment and model settings. Code can use the Republic bridge and media attachment tools described in [CodeMode](/docs/sdk/tools/#code-mode). Model credentials remain on the host when using the bridge; the default local execution environment still runs code on that host. - -`BubAgent` interprets `state["code_mode"]` and the `deferred` flag to apply these selection rules. With a plain `core.Agent`, [compose CodeMode explicitly](/docs/sdk/tools/#code-mode) and supply your own deferred-loading policy if you need one. +The app connects CodeMode to the session's environment and model settings. Code can use the Republic bridge and media attachment tools described in [CodeMode](/docs/sdk/tools/#compose-tools-with-codemode). Model credentials remain on the host when using the bridge; the default local execution environment still runs code on that host. - +`BubAgent` interprets `state["code_mode"]` and the `deferred` flag to apply these selection rules. With a plain `core.Agent`, [compose CodeMode explicitly](/docs/sdk/tools/#compose-tools-with-codemode) and supply your own deferred-loading policy if you need one. ## Choose the execution environment Builtin `bash`, `bash.output`, `bash.kill`, `fs.*`, and `run_code` use the session's `Environment`. The default is `LocalEnvironment`, which runs on the host. -Provide another environment through a plugin's `provide_environment(session_id, workspace)` hook. The framework caches one environment per session and closes it when `framework.running()` exits. [Tools and CodeMode](/docs/sdk/tools/#run-tools-in-an-environment) describes the environment interface. +Provide another environment through a plugin's `provide_environment(session_id, workspace)` hook. The framework caches one environment per session and closes it when `framework.running()` exits. [Tools and CodeMode](/docs/sdk/tools/#choose-an-execution-environment) describes the environment interface. A custom application tool can access that environment with `environment_from_state(context.state)` from `bub.builtin.environment`. The Python tool runs on the host and explicitly delegates operations to the environment when needed. @@ -105,4 +99,4 @@ A custom application tool can access that environment with `environment_from_sta Put workspace-wide instructions in `AGENTS.md`. Use [skills](/docs/app/skills/) for procedures the model can load for a task. A skill can tell the model when to call your tool; the Python function defines what that tool does. -Comma commands let a person invoke registered tools without asking the model. See [text commands](/docs/sdk/tools/#commands) for command syntax. +Comma commands let a person invoke registered tools without asking the model. See [text commands](/docs/sdk/tools/#call-a-tool-without-a-model) for command syntax. diff --git a/website/src/content/docs/docs/concepts/events.mdx b/website/src/content/docs/docs/concepts/events.mdx index 64cf162af..86683fc78 100644 --- a/website/src/content/docs/docs/concepts/events.mdx +++ b/website/src/content/docs/docs/concepts/events.mdx @@ -15,8 +15,6 @@ Three kinds of information have three different jobs: The agent loop controls execution. It calls the event handlers at fixed points, before it continues. TapeRecorder saves selected exchanges and lifecycle records. Your handlers can record the other events that you need to keep. - - ## Handlers This example uses the `agent` from the [SDK guide](/docs/sdk/): @@ -43,8 +41,6 @@ For each emission, the matching handlers run in the order of registration. The b This order applies to one emission. Concurrent tool calls or runs can emit events at the same time. Shared handler state therefore needs its own coordination. - - ## Call control To stop a call, assign a decision to the `decision` field of the event: @@ -77,12 +73,10 @@ To change the arguments and continue, replace `event.call` with `dataclasses.rep | `ChatFinished` | The exchange of a step is ready to record. | Read the exchange. | | `CommandFinished` | A direct command finishes or fails. | Read the command, the status, and the output. | -Command prompts run tools directly. They emit tool events and `CommandFinished`. See [Commands](/docs/sdk/tools/#commands). +Command prompts run tools directly. They emit tool events and `CommandFinished`. See [Commands](/docs/sdk/tools/#call-a-tool-without-a-model). All agent events carry the turn `state`. Events that derive from `TapeEvent` also carry the active tape. For tool events, the tool context is available as `event.call.context` when the call has one. - - ## Recording The Agent registers TapeRecorder before your handlers. It records `RunStarted`, `RunFinished`, `StepStarted`, `StepFinished`, `ChatFinished`, and `CommandFinished`. The `ChatFinished` handler writes the messages and tool exchanges that later model calls read as context. @@ -91,8 +85,6 @@ Recording is part of normal operation. It writes to the configured store. That s To save a custom event, add a handler that writes it to the tape. Use the interception events to change a request or a result. A `ChatFinished` observer runs after the default recorder has saved that exchange. - - ## Custom events Define a dataclass that derives from `AgentEvent`. Derive it from `TapeEvent` when the handlers need a tape. This example records a business event: @@ -122,11 +114,9 @@ async def mark_reviewed() -> None: `emit()` returns the event after the handlers have run. A custom mutable event can carry a response back to its emitter. Override its `stopped` property if delivery must end when that response is set. Tools can emit events through `context.events`, with the same turn state. - - ## App plugins -`AgentPlugins` connects four hooks to the call events: `before_llm_call`, `after_llm_call`, `before_tool_call`, and `after_tool_call`. It connects the `continue_prompt` hook to `StepFinished`. It connects the steering inbox to `MessagesPending`. A plugin returns the values that the hook contract requires, and the adapter updates the event. `BubAgent` uses this adapter, and a core Agent can [use it too](/docs/sdk/reuse-plugins/#agent-plugins). +`AgentPlugins` connects four hooks to the call events: `before_llm_call`, `after_llm_call`, `before_tool_call`, and `after_tool_call`. It connects the `continue_prompt` hook to `StepFinished`. It connects the steering inbox to `MessagesPending`. A plugin returns the values that the hook contract requires, and the adapter updates the event. `BubAgent` uses this adapter, and a core Agent can [use it too](/docs/sdk/reuse-plugins/#other-hooks). This adapter follows the plugin hook order. It catches exceptions from individual interception hooks, logs them, and skips them. To deny a call, return the correct decision. The other app hooks follow their own error contracts. diff --git a/website/src/content/docs/docs/concepts/extension-points.mdx b/website/src/content/docs/docs/concepts/extension-points.mdx index 3dd621e4b..da6d6b48e 100644 --- a/website/src/content/docs/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/docs/concepts/extension-points.mdx @@ -20,7 +20,7 @@ A tool runs Python and returns a result. A skill is a directory with a `SKILL.md A plain Agent uses the tools and prompt sections that you give it. The Bub app discovers skills and reads `AGENTS.md`. See [Skills](/docs/app/skills/) and [Tools](/docs/app/tools/). -The agent can also reach a tool through a [text command](/docs/sdk/tools/#commands), or through Python that the model writes with CodeMode. All these paths use the same tool implementation. +The agent can also reach a tool through a [text command](/docs/sdk/tools/#call-a-tool-without-a-model), or through Python that the model writes with CodeMode. All these paths use the same tool implementation. ## Messages and state diff --git a/website/src/content/docs/docs/concepts/tape.mdx b/website/src/content/docs/docs/concepts/tape.mdx index bd737d187..dc4838d0a 100644 --- a/website/src/content/docs/docs/concepts/tape.mdx +++ b/website/src/content/docs/docs/concepts/tape.mdx @@ -7,8 +7,6 @@ An agent needs two things. It needs a record of its work. It also needs a useful `core.Agent` and `BubAgent` share this model. Their default stores and session names are different. - - ## Storage A plain `Agent` creates an `InMemoryTapeStore` by default. That history lives as long as the store. To keep it between processes, pass a file store: @@ -45,8 +43,6 @@ To use your own store, implement `TapeStore` or `AsyncTapeStore` from `bub.core. Use synchronous methods for `TapeStore` and asynchronous methods for `AsyncTapeStore`. The agent adapts a synchronous store for async use. Your store and your app handle persistence and coordination between processes. - - ## Context Tape entries are messages, tool calls, tool results, events, and anchors. Normal recording only appends entries. To clear history, call an explicit operation such as `Tape.reset()`. @@ -77,16 +73,12 @@ To choose another anchor, pass `context=TapeContext(...)` to `Agent`. You can al When the agent recognizes a context-length error, it makes one automatic handoff. It then retries the prompt with the shorter context. If you need a summary of the earlier history, create it before the handoff. The retry can still fail if the request is too large. - - ## State The `state` that you pass to `agent.run()` is a mutable dictionary. Tools, prompt sections, and event handlers share it during the run. Your app owns its persistence. Keep durable data in your own store, or write selected facts to the tape and read them back when you need them. The default [TapeRecorder](/docs/concepts/events/#recording) saves conversation exchanges and selected lifecycle records. To keep other variables, custom events, or decisions, record them yourself. - - ## Forks Each `agent.run()` opens a fork of its tape. When the stream closes, the fork appends its new entries to the parent tape. This also happens after an exception or an early cancellation. A failed run can therefore leave useful partial history. @@ -103,8 +95,6 @@ A `TapeSidecar` stores related data in a separate tape. A mounted sidecar follow The Bub app mounts a spill sidecar for large tool results. It keeps a short preview and a handle in the conversation. The agent can then read parts of the result with `spill.read`. If a sidecar fails during a fork merge or a tape reset, the app records the failure separately. The main tape operation can still complete. The spill limits are in the [app configuration](/docs/app/configure/). - - ## App storage `BubAgent` uses the store that `BubFramework` provides. The built-in app uses `FuzzyFileTapeStore` under the Bub home directory, normally `~/.bub/tapes/`. The tape name combines hashes of the workspace path and the session ID. The same session ID in two workspaces therefore has two separate histories. diff --git a/website/src/content/docs/docs/integrations/index.mdx b/website/src/content/docs/docs/integrations/index.mdx index 6cd77783f..d1257ac61 100644 --- a/website/src/content/docs/docs/integrations/index.mdx +++ b/website/src/content/docs/docs/integrations/index.mdx @@ -19,6 +19,6 @@ These guides use the app's CLI, configuration, or plugin system. Start with the - [Use a local llama.cpp model](/docs/integrations/local-models/): point the app at a local model server. - [Store conversation history in SQLite](/docs/integrations/storage/): replace the app's file store through a plugin. - [Observability](/docs/integrations/observability/): inspect local records or export traces to Phoenix and Logfire. -- [WASM plugins](/docs/app/plugins/#wasm-plugins): write a hook in Go or Rust and run it through WebAssembly. +- [WASM plugins](/docs/app/plugins/#wasm-plugins-experimental): write a hook in Go or Rust and run it through WebAssembly. Each integration guide links to the plugin's documentation for package-specific setup. diff --git a/website/src/content/docs/docs/reference/hooks.mdx b/website/src/content/docs/docs/reference/hooks.mdx index 0f4742180..678aa8fbe 100644 --- a/website/src/content/docs/docs/reference/hooks.mdx +++ b/website/src/content/docs/docs/reference/hooks.mdx @@ -5,7 +5,7 @@ description: Hook signatures, dispatch rules, and resource lifecycles for Bub pl Hooks connect plugins to the Bub application. This reference lists the 26 hooks declared by `BubHookSpecs`. For examples, see [Hooks](/docs/app/hooks/); for the order of application stages, see [Turn](/docs/concepts/turn/). -`first` below means the first non-`None` result in hook priority order. `all` means every implementation is called. An ordinary hook exception stops that dispatch and propagates; the [agent interception hooks](#agent-loop-hooks-pluginagenthooks) and `on_error` have separate exception handling. +`first` below means the first non-`None` result in hook priority order. `all` means every implementation is called. An ordinary hook exception stops that dispatch and propagates; the [agent interception hooks](#interception-through-pluginagenthooks) and `on_error` have separate exception handling. ## Turn hooks @@ -57,8 +57,6 @@ Synchronous dispatch skips awaitable return values with a `hook.async_not_suppor - - ## Priority and dispatch Bub registers builtin hooks first, then installed entry-point plugins. For ordinary implementations at the same priority, later registrations run first. `@hookimpl(tryfirst=True)` and `@hookimpl(trylast=True)` alter priority. These flags and registration order together determine which implementation runs first. @@ -87,8 +85,6 @@ The builtin lifespan owns background shell processes started within that scope. Tape sidecar providers contribute named sibling tapes. Bub mounts them on the application's root Tape and applies fork, merge, archive, and reset to mounted sidecars. Stored data remains after a provider is removed. See [Tape and context](/docs/concepts/tape/) for shared storage semantics. - - ## Interception through PluginAgentHooks @@ -96,7 +92,7 @@ Tape sidecar providers contribute named sibling tapes. Bub mounts them on the ap The before hooks run in hook priority order. Each sees updates made by earlier implementations. `LLMCallDecision.finish(text)`, `ToolCallDecision.replace(result)`, and `ToolCallDecision.deny(message)` stop the remaining before implementations. A finished LLM decision never calls the provider and has no matching `after_llm_call`. -The adapter catches ordinary exceptions from each interception hook, logs `hook.agent_hook_failed`, and continues. Return a decision to deny a call. Handlers registered directly on the [core EventBus](/docs/concepts/events/#delivery) propagate their exceptions. +The adapter catches ordinary exceptions from each interception hook, logs `hook.agent_hook_failed`, and continues. Return a decision to deny a call. Handlers registered directly on the [core EventBus](/docs/concepts/events/#handlers) propagate their exceptions. After hooks observe successful calls and ordinary failures. Cancellation and consumer close bypass them. `after_tool_call` also runs for denied or replaced calls; implementations share the same result object. Changing `result.result` changes the value passed onward, while `result.error` retains the original failure. diff --git a/website/src/content/docs/docs/reference/python-api.mdx b/website/src/content/docs/docs/reference/python-api.mdx index 9026b4112..c6aa30a8f 100644 --- a/website/src/content/docs/docs/reference/python-api.mdx +++ b/website/src/content/docs/docs/reference/python-api.mdx @@ -9,8 +9,6 @@ Look up an interface by what it belongs to. Core interfaces work in a Python pro Interfaces for the core loop. They do not load application configuration. - - ### Streams ```python @@ -50,7 +48,7 @@ from bub.hooks.interception import ( ) ``` -`BubError.kind` identifies the error category; `as_dict()` returns a payload suitable for transport. Interception payloads are shared with `bub.core.events`. Their use by plugins, including exception handling, is defined in [Hooks](/docs/reference/hooks/#agent-loop-hooks-pluginagenthooks). +`BubError.kind` identifies the error category; `as_dict()` returns a payload suitable for transport. Interception payloads are shared with `bub.core.events`. Their use by plugins, including exception handling, is defined in [Hooks](/docs/reference/hooks/#interception-through-pluginagenthooks). ## Application @@ -143,7 +141,7 @@ from bub.builtin.agent import BubAgent Application configuration is process-wide. Construct a `BubAgent` inside `framework.running()` when it should use the framework's active store. An explicitly supplied store takes precedence and remains the caller's lifecycle responsibility. Without either store, the agent uses memory. -`run_stream` loads initial state when the caller omits `state` and uses caller-supplied state directly otherwise. The host owns save-state hooks, outbound handling, and concurrent turn scheduling. See [Embed the application](/docs/app/distribution/#programmatic-embedding) for complete usage. +`run_stream` loads initial state when the caller omits `state` and uses caller-supplied state directly otherwise. The host owns save-state hooks, outbound handling, and concurrent turn scheduling. See [Embed the application](/docs/app/distribution/#embed-the-application) for complete usage. ## Imports diff --git a/website/src/content/docs/docs/sdk/index.md b/website/src/content/docs/docs/sdk/index.md index e99d241dc..fabc471fe 100644 --- a/website/src/content/docs/docs/sdk/index.md +++ b/website/src/content/docs/docs/sdk/index.md @@ -7,8 +7,6 @@ Choose a model, add tools, and connect the agent to your program. `bub.core.Agen This page assumes Bub is installed for the agent path; see [Install Bub](/docs/getting-started/install/#agent-install). The [Bub application](/docs/app/workspace/) builds on the same core and adds configuration and plugin discovery. - - ## Run the example Save this as `sdk_example.py` in your project: @@ -64,8 +62,6 @@ The tool returns a demonstration order status. Replace its body with your own or Run the program again with the same `tape` name to continue the conversation stored under `sessions/`. Change the name to start a separate conversation. - - ## Manage the run and its stream `agent.run()` yields a stream. Iterating the stream drives the loop: call the model, run its requested tools, then call the model again until it answers without further tool calls. A `final` event ends one model step; exhaust the stream to finish the run. diff --git a/website/src/content/docs/docs/sdk/options.mdx b/website/src/content/docs/docs/sdk/options.mdx index d0f8ad0c2..ba5bc247b 100644 --- a/website/src/content/docs/docs/sdk/options.mdx +++ b/website/src/content/docs/docs/sdk/options.mdx @@ -5,8 +5,6 @@ description: Choose the model, the instructions, and the other options that shap These options shape how an agent runs. Start from the [basic agent](/docs/sdk/) example, then change the options you need. - - ## Choose a model Pass a Republic chat model to `Agent(model=...)` for a default, or to `agent.run(model=...)` for one run. A run that needs a model raises `ValueError` when neither is supplied. You own the model's lifetime, so open it with `async with` around the runs that use it. @@ -35,8 +33,6 @@ For your own fallback, credential, or timeout policy, implement `bub.core.ModelC A `LLMCallStarted` handler can change the requested model. With a Republic chat model, a change within the same provider reuses its client; a different provider uses a client configured from Republic's environment. Supply a `ModelClient` when this policy needs to differ. - - ## Set the instructions `system_prompt` accepts text, a function, or an ordered list of either. Functions receive a `PromptContext` and may be synchronous or asynchronous. The agent renders the sections once per run, before its first model step, omits empty results, and joins the rest with blank lines. Later steps of the run reuse that text. @@ -54,15 +50,8 @@ def customer(context: PromptContext) -> str | None: `PromptContext` carries the run's prompt, model-facing tools, model spec, tape, turn state, and agent. `available_tools` lists the agent's registered tools. Remove a section with `agent.system_prompt.remove(section)`. - - - The `state` passed to a run is a mutable dictionary shared by tools, prompt sections, and event handlers. You decide which values to save and how to restore them for another run. See [Turn state](/docs/concepts/tape/#state) for what to keep between runs. - - - - ## Set the other parameters | Parameter | Default and purpose | @@ -80,8 +69,6 @@ The `state` passed to a run is a mutable dictionary shared by tools, prompt sect The agent keeps supplied `Tool` objects in `agent.tools` by name and provider tools in `agent.builtin_tools`. You select its tools explicitly when constructing or updating the agent. - - ## Customize how a step is prepared Subclass `Agent` and override `prepare_tools(tape, prompt)` when selecting tools needs application logic. By default it offers the supplied tools except those marked `exposure="command"`. The agent prepares tools once per run, before its first model step, and offers the same tools in every step; a tool call outside them is resolved with a fresh `prepare_tools(tape, None)`, so tools that become available during the run are still callable. The tape's `context.state` is the run's state. @@ -111,6 +98,3 @@ def check_results(event: StepFinished) -> None: ``` `agent.session(name, state=...)` opens a tape whose lifecycle you manage. Pass that `Tape` object, not its name, to `agent.run_on(tape, prompt, model=...)` or to `agent.run_once(prompt, model=..., tape=)`. `run_on()` returns a stream you must close. `run_once()` yields one model step when you drive the loop yourself. - - - diff --git a/website/src/content/docs/docs/sdk/reuse-plugins.mdx b/website/src/content/docs/docs/sdk/reuse-plugins.mdx index 292dad433..0b8d7ceaa 100644 --- a/website/src/content/docs/docs/sdk/reuse-plugins.mdx +++ b/website/src/content/docs/docs/sdk/reuse-plugins.mdx @@ -5,7 +5,7 @@ description: Bring plugin tools and hooks into your own agent while keeping your Use Bub's application plugins in your existing Python service. Your host keeps its HTTP routes, task handlers, and output handling. Choose the agent and runtime according to the application behavior you want to reuse. -`BubAgent` is Bub's existing combination of the core loop, settings, plugin hooks, skills, and tool policies. Its [direct stream entry point](#app-runtime) also works inside a host application. Use `BubFramework.process_inbound()` when you want the full message pipeline. +`BubAgent` is Bub's existing combination of the core loop, settings, plugin hooks, skills, and tool policies. Its [direct stream entry point](#reuse-bubagent) also works inside a host application. Use `BubFramework.process_inbound()` when you want the full message pipeline. With `core.Agent`, you choose the components and connect the hooks yourself. The examples below take a search tool from one plugin and adapt the call hooks of another. Each example covers that plugin's dependencies; background services need their own startup and shutdown. @@ -109,8 +109,6 @@ Register and attach once during service startup. This plugin runs entirely throu The adapter catches ordinary plugin exceptions and skips the failed hook. A hook that blocks a tool must return `ToolCallDecision.deny(...)`; see [Agent events](/docs/concepts/events/) for interception semantics. - - ### Other hooks The other hooks decide what the run has. Each hook maps to a core Agent extension point: @@ -148,16 +146,14 @@ The plugin's implementation determines what your host must provide: | Plugin capability | How to reuse it | | --- | --- | | Tools or a store | Import the public component and pass it to Agent. For example, `bub_tapestore_sqlalchemy.store.SQLAlchemyTapeStore` accepts a database URL and can be passed as `store=`. | -| Model or tool interception, system prompt, continuation, or steering | Register the selected plugin and [attach `AgentPlugins`](#agent-plugins). Check any state fields or tools the plugin expects. | -| Message preparation, model-stage replacement, state saving, or delivery | [Embed the application pipeline](/docs/app/distribution/#programmatic-embedding) in your host. It runs these stages and returns a `TurnResult`. | +| Model or tool interception, system prompt, continuation, or steering | Register the selected plugin and [attach `AgentPlugins`](#other-hooks). Check any state fields or tools the plugin expects. | +| Message preparation, model-stage replacement, state saving, or delivery | [Embed the application pipeline](/docs/app/distribution/#embed-the-application) in your host. It runs these stages and returns a `TurnResult`. | | Background connections or discovered tools | Open the service, wait until it is ready, attach its tools, then close it with the host. Follow the plugin's own lifecycle API. | Follow the chosen store's lifecycle contract. `framework.running()` acquires hook-provided resources and closes the context managers it entered. A store returned as an ordinary object keeps its own cleanup requirements. For MCP, follow the plugin's [embedding example](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-mcp#embedding): connect with `MCPChannel.connect()`, attach tools to a `BubAgent` with `bind_agent()`, and close connections with `stop()`. Your host must manage this lifecycle in addition to `framework.running()`. - - ## Reuse BubAgent `BubAgent` combines the core loop with Bub settings, skills, tool policies, and plugin hooks. A host can load installed plugins with `framework.load_hooks()`, enter `framework.running()`, then construct `BubAgent(framework)`. Its `run_stream(*, session_id, prompt, state=None, ...)` returns a stream for the host to consume and close. diff --git a/website/src/content/docs/docs/sdk/tools.mdx b/website/src/content/docs/docs/sdk/tools.mdx index 29067fb0b..ac446401e 100644 --- a/website/src/content/docs/docs/sdk/tools.mdx +++ b/website/src/content/docs/docs/sdk/tools.mdx @@ -7,8 +7,6 @@ A tool gives your agent an action: look up an order, read a file, or call a serv This page covers the shared tool contract and direct SDK use. For installation and a complete model loop, start with [Build your own agent](/docs/sdk/). - - ## Define the action `Tool.from_callable()` reads parameter types from annotations and a description from the docstring. It accepts synchronous and asynchronous functions and returns a tool for you to pass to an Agent. @@ -29,9 +27,7 @@ agent = Agent(tools=[add_tool]) The runtime keeps the name `math.add`; model-facing names replace dots with underscores, so the model sees `math_add`. Use `description=` to supply a description explicitly. -Plain `Agent` offers all supplied tools except those marked `exposure="command"`. Deferred loading, the `allowed_tools` state value, and the `state["code_mode"]` switch are [BubAgent policies](/docs/app/tools/). To choose tools per run in your own agent, override [`prepare_tools()`](/docs/sdk/options/#customize-the-loop). - - +Plain `Agent` offers all supplied tools except those marked `exposure="command"`. Deferred loading, the `allowed_tools` state value, and the `state["code_mode"]` switch are [BubAgent policies](/docs/app/tools/). To choose tools per run in your own agent, override [`prepare_tools()`](/docs/sdk/options/#customize-how-a-step-is-prepared). ## Return data the caller can use @@ -59,9 +55,7 @@ order_tool = Tool.from_callable( `Tool.render(result)` applies the renderer. By default, strings pass through, Pydantic models and JSON-serializable values become JSON, and other values use `str()`. The model-facing executor renders before `ToolCallFinished`, so handlers see the result that will be sent to the model. The executor used by CodeMode keeps structured results intact. -Tool execution errors become tool results in the model loop, giving the model a chance to respond or retry. A direct command call propagates its error to the caller. [Event handlers](/docs/concepts/events/#delivery) have their own exception behavior. - - +Tool execution errors become tool results in the model loop, giving the model a chance to respond or retry. A direct command call propagates its error to the caller. [Event handlers](/docs/concepts/events/#handlers) have their own exception behavior. ## Use the current conversation @@ -82,8 +76,6 @@ remember_tool = Tool.from_callable(remember, context=True) This writes a business event to the tape. The default context selector reads conversation messages and tool exchanges; including this event in a model request requires your own selection logic. Use [Tape and context](/docs/concepts/tape/#context) to decide which saved information the model should read. - - ## Compose tools with CodeMode CodeMode lets the model write Python that calls your tools. It provides a `run_code` tool and a prompt section describing the tools available inside that code. Add both explicitly to a plain Agent: @@ -101,7 +93,7 @@ agent = Agent( ) ``` -Run this agent with the model lifecycle shown in [Configure the agent](/docs/sdk/options/#models). The model can call `run_code` with code such as: +Run this agent with the model lifecycle shown in [Configure the agent](/docs/sdk/options/#choose-a-model). The model can call `run_code` with code such as: ```python order = await tools.lookup_order(order_id="A123") @@ -125,8 +117,6 @@ The code receives a Republic bridge for chat, embedding, and decision model call `run_code` returns printed output and attached media. Its default timeout is 120 seconds; callers may pass `timeout_seconds`. With `LocalEnvironment`, each call starts a Python process with the host user's permissions and stops that process and its descendants on completion or cancellation. Choose another environment when you need a different execution boundary. - - ## Choose an execution environment The `Environment` interface in `bub.environment` supplies process execution, file access, and Python execution. `CodeMode(environment=...)` uses the environment you provide. In the Bub application, the [`provide_environment` hook](/docs/reference/hooks/) selects an environment per session. @@ -141,8 +131,6 @@ The `Environment` interface in `bub.environment` supplies process execution, fil The environment runs the generated code and calls back to the host to execute your Python tools. Close custom environments when your application stops using them. - - ## Call a tool without a model Set `command_prefix` to accept text commands. This example uses the `add_tool` defined above and needs no model credentials: diff --git a/website/src/content/docs/zh-cn/docs/app/configure.mdx b/website/src/content/docs/zh-cn/docs/app/configure.mdx index cbbb531ad..3f35d9d1f 100644 --- a/website/src/content/docs/zh-cn/docs/app/configure.mdx +++ b/website/src/content/docs/zh-cn/docs/app/configure.mdx @@ -66,8 +66,6 @@ Republic 从 `$CODEX_HOME/auth.json` 或 `~/.codex/auth.json` 读取并刷新 Co 完整变量表(包括渠道防抖参数和高级模型客户端设置)见 [设置参考](/zh-cn/docs/reference/settings/)。 - - ## 4. 配置渠道凭据 每个渠道有自己的配置前缀。内置 Telegram 渠道读取 `BUB_TELEGRAM_*`: diff --git a/website/src/content/docs/zh-cn/docs/app/distribution.mdx b/website/src/content/docs/zh-cn/docs/app/distribution.mdx index 097b50347..de8cf1dc5 100644 --- a/website/src/content/docs/zh-cn/docs/app/distribution.mdx +++ b/website/src/content/docs/zh-cn/docs/app/distribution.mdx @@ -7,8 +7,6 @@ description: 嵌入应用框架,或发布包含自有插件和技能的 CLI。 这条路径使用[安装 Bub](/zh-cn/docs/getting-started/install/)中说明的应用依赖。如果希望直接组合 Agent 和各个组件,请从[构建自己的 Agent](/zh-cn/docs/sdk/)开始。 - - ## 嵌入应用 用 `BubFramework.process_inbound` 执行应用的完整 turn:解析会话、加载状态、准备提示词、执行、保存状态、生成并发送出站消息。 @@ -43,8 +41,6 @@ asyncio.run(main()) `process_inbound` 返回已经完成的 `TurnResult`。设置 `stream_output=True` 后,它先通过绑定的渠道 router 消费流,再返回结果。宿主程序负责并发消息调度,并按需要管理会话锁。 - - ## 在自己的服务中复用插件 宿主可以使用插件提供的工具、存储或 hooks,同时保留自己的 API 和流处理方式。按[复用插件](/zh-cn/docs/sdk/reuse-plugins/)中的步骤,把这些能力接到 `core.Agent` 上。 @@ -85,6 +81,6 @@ channels = "my_bub.channels:ChannelPlugin" packages = ["src/my_bub", "src/skills"] ``` -每个目标都遵循[插件注册规则](/zh-cn/docs/app/plugins/)。任务指令按 [技能包目录结构](/zh-cn/docs/app/skills/#ship-skills-with-your-package)加入发行包。 +每个目标都遵循[插件注册规则](/zh-cn/docs/app/plugins/)。任务指令按 [技能包目录结构](/zh-cn/docs/app/skills/#把技能放进-wheel)加入发行包。 发布前,把构建好的 wheel 安装到独立环境,运行命令、核对插件报告、加载打包的技能,再完成一次有代表性的对话。对于宿主应用,还应检查流或后台工具运行期间的关闭行为。 diff --git a/website/src/content/docs/zh-cn/docs/app/hooks.mdx b/website/src/content/docs/zh-cn/docs/app/hooks.mdx index dc556edc7..6e91755ef 100644 --- a/website/src/content/docs/zh-cn/docs/app/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/app/hooks.mdx @@ -36,8 +36,6 @@ def system_prompt(prompt, state): return "Explain which files support your answer." ``` - - ## 控制一次模型或工具调用 保留内置 agent,并调整某次调用时,使用 agent 拦截 hooks。例如,限制一次请求的输出 token 数: @@ -70,7 +68,7 @@ def before_tool_call(call: ToolCall, state): 由你的授权逻辑设置 `can_cancel_orders`,hook 根据这个值允许或拒绝调用。 -这些拦截 hooks 抛出的异常会被记录并跳过。要控制执行,使用 `deny`、`replace` 或 `LLMCallDecision.finish`。[拦截契约](/zh-cn/docs/reference/hooks/#agent-loop-hooks-pluginagenthooks)说明了 decision 何时终止调用链。 +这些拦截 hooks 抛出的异常会被记录并跳过。要控制执行,使用 `deny`、`replace` 或 `LLMCallDecision.finish`。[拦截契约](/zh-cn/docs/reference/hooks/#通过-pluginagenthooks-拦截调用)说明了 decision 何时终止调用链。 ## 观察已经完成的调用 @@ -90,7 +88,7 @@ def after_tool_call(call, result, state): print(f"tool={call.tool} duration_ms={result.duration_ms} error={result.error!r}") ``` -这些 hooks 观察已经完成的调用,包括普通异常导致的失败,但不报告取消。需要逐块处理输出时,在宿主程序或渠道中使用流接口,并遵守[关闭规则](/zh-cn/docs/reference/python-api/#streaming)。 +这些 hooks 观察已经完成的调用,包括普通异常导致的失败,但不报告取消。需要逐块处理输出时,在宿主程序或渠道中使用流接口,并遵守[关闭规则](/zh-cn/docs/reference/python-api/#流)。 如果要替换整个模型阶段,实现 `run_model` 或 `run_model_stream` 中的一个。[第一个插件](/zh-cn/docs/app/plugins/)演示了文本替换。替换实现负责自己的执行和记录行为;只有使用内置 agent 时,才会运行它的拦截 hooks。 @@ -122,8 +120,6 @@ turn_counter = TurnCounter() `save_state` 位于模型阶段的 `finally` 块中,前提是会话解析、状态加载和提示词准备已经成功。这些准备阶段发生错误时,会跳过保存 hook。会话历史另有 [Tape 生命周期](/zh-cn/docs/concepts/tape/#state)。 - - ## 提供会话存储 diff --git a/website/src/content/docs/zh-cn/docs/app/plugins.mdx b/website/src/content/docs/zh-cn/docs/app/plugins.mdx index 93bc18c8b..2472b08eb 100644 --- a/website/src/content/docs/zh-cn/docs/app/plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/app/plugins.mdx @@ -88,7 +88,7 @@ uv run bub run "hello from my plugin" 如果没有看到 `echo`,检查安装插件的环境是否就是运行 `bub` 的环境、入口点组名,以及启动日志中的导入错误。修改包元数据后,需要重新安装包。如果已经列出却返回其他回复,检查其他模型 hooks 及其优先级。 -Bub 先注册内置 hooks,再注册入口点插件。同等优先级通常按注册顺序的逆序调用;`tryfirst` 和 `trylast` 会影响顺序。[调用规则](/zh-cn/docs/reference/hooks/#iteration-order)说明了哪个实现先运行。 +Bub 先注册内置 hooks,再注册入口点插件。同等优先级通常按注册顺序的逆序调用;`tryfirst` 和 `trylast` 会影响顺序。[调用规则](/zh-cn/docs/reference/hooks/#优先级与分发)说明了哪个实现先运行。 ## 选择入口点形式 @@ -159,8 +159,6 @@ from . import tools # noqa: F401 可移植的 Agent Plugins 使用另一种格式,包括 `plugin.json`、技能和 MCP servers。[Agent Plugins 集成](/zh-cn/docs/integrations/agent-plugins/)通过一个 Python 插件加载这些包。 - - ## WASM 插件(实验性) 插件不一定要用 Python 写。[`bub-extism`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) 插件运行 WebAssembly 模块,把模块导出的函数当作 hook。hook 的分发和优先级仍由 Bub 决定,Extism 只负责调用你的模块。 diff --git a/website/src/content/docs/zh-cn/docs/app/skills.mdx b/website/src/content/docs/zh-cn/docs/app/skills.mdx index b2b7abf8a..42fa6600c 100644 --- a/website/src/content/docs/zh-cn/docs/app/skills.mdx +++ b/website/src/content/docs/zh-cn/docs/app/skills.mdx @@ -83,8 +83,6 @@ Bub 发现技能时会检查这些字段: 系统提示词会列出可用技能。`$skill-name` 在当前轮展开该技能;`,skill name=skill-name` 则通过命令路径打印正文。 - - ## 把技能放进 wheel diff --git a/website/src/content/docs/zh-cn/docs/app/tools.mdx b/website/src/content/docs/zh-cn/docs/app/tools.mdx index 7a623cf45..e4712b6f2 100644 --- a/website/src/content/docs/zh-cn/docs/app/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/app/tools.mdx @@ -42,9 +42,7 @@ class OrdersPlugin: 调用 `BubAgent.run_stream` 时,传入 `allowed_tools` 限制该 turn 里模型可用的工具。它接受运行时名称或模型名称。`None` 允许全部实例工具,空集合不允许任何工具。未知名称会抛出 `ValueError`。 -这个筛选适用于模型和 code mode 中的工具。逗号命令直接使用 agent 实例的工具。如果规则也需要约束命令执行,使用[工具调用 decision](/zh-cn/docs/app/hooks/#control-calls)。 - - +这个筛选适用于模型和 code mode 中的工具。逗号命令直接使用 agent 实例的工具。如果规则也需要约束命令执行,使用[工具调用 decision](/zh-cn/docs/app/hooks/#控制一次模型或工具调用)。 ## 按需加载工具 @@ -64,8 +62,6 @@ def get_order(order_id: str) -> dict[str, str]: turn 的允许列表同时约束工具的展示和加载。code mode 中,允许使用的 `auto` 和 `code` 工具立即可供 Python 调用;延迟策略只作用于仍直接提供给模型的工具。 - - ## 在应用中使用 code mode 为当前会话启用 code mode: @@ -87,17 +83,15 @@ turn 的允许列表同时约束工具的展示和加载。code mode 中,允 如果排除了 `run_code`,该 turn 按关闭 code mode 时的规则选择工具。应用把 Python 工具声明文件写入 `/codemode/`,并把路径加入系统提示词。`await tools.orders_get(order_id="123")` 这样的 Python 调用使用已选工具,并经过应用的拦截 hooks。 -应用把 CodeMode 接到会话的执行环境和模型设置上。代码可以使用 [CodeMode](/zh-cn/docs/sdk/tools/#code-mode) 中介绍的 Republic bridge 和媒体附加工具。通过 bridge 使用模型时,凭据保留在宿主侧;默认的本地执行环境仍在这个宿主上运行代码。 - -`BubAgent` 读取 `state["code_mode"]` 和 `deferred` 标记,执行这些筛选规则。使用普通 `core.Agent` 时,需要[显式组合 CodeMode](/zh-cn/docs/sdk/tools/#code-mode);如需延迟加载,也由你提供相应策略。 +应用把 CodeMode 接到会话的执行环境和模型设置上。代码可以使用 [CodeMode](/zh-cn/docs/sdk/tools/#组合工具与-codemode) 中介绍的 Republic bridge 和媒体附加工具。通过 bridge 使用模型时,凭据保留在宿主侧;默认的本地执行环境仍在这个宿主上运行代码。 - +`BubAgent` 读取 `state["code_mode"]` 和 `deferred` 标记,执行这些筛选规则。使用普通 `core.Agent` 时,需要[显式组合 CodeMode](/zh-cn/docs/sdk/tools/#组合工具与-codemode);如需延迟加载,也由你提供相应策略。 ## 选择执行环境 内置 `bash`、`bash.output`、`bash.kill`、`fs.*` 和 `run_code` 使用会话的 `Environment`。默认是运行在宿主上的 `LocalEnvironment`。 -插件通过 `provide_environment(session_id, workspace)` hook 提供其他环境。Framework 为每个会话缓存一个环境,并在退出 `framework.running()` 时关闭。[工具与 CodeMode](/zh-cn/docs/sdk/tools/#run-tools-in-an-environment)说明环境接口。 +插件通过 `provide_environment(session_id, workspace)` hook 提供其他环境。Framework 为每个会话缓存一个环境,并在退出 `framework.running()` 时关闭。[工具与 CodeMode](/zh-cn/docs/sdk/tools/#选择执行环境)说明环境接口。 自定义应用工具可以用 `bub.builtin.environment` 中的 `environment_from_state(context.state)` 取得环境。Python 工具本身在宿主侧运行,需要时再显式调用环境来完成相应操作。 @@ -105,4 +99,4 @@ turn 的允许列表同时约束工具的展示和加载。code mode 中,允 把工作区通用指令写入 `AGENTS.md`,用 [技能](/zh-cn/docs/app/skills/)提供按任务加载的流程。技能可以告诉模型何时调用工具,Python 函数则定义工具执行的动作。 -逗号命令让使用者无需请求模型,就能调用已注册的工具。语法见[文本命令](/zh-cn/docs/sdk/tools/#commands)。 +逗号命令让使用者无需请求模型,就能调用已注册的工具。语法见[文本命令](/zh-cn/docs/sdk/tools/#不经过模型调用工具)。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/events.mdx b/website/src/content/docs/zh-cn/docs/concepts/events.mdx index 7a4d1681d..01afece7a 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/events.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/events.mdx @@ -15,8 +15,6 @@ Agent 事件能让你看到一次运行,也能让你在模型或工具调用 循环控制执行,并在固定位置调用事件处理器,然后才往下走。TapeRecorder 保存选定的往返和生命周期记录。其他你要留的事件,由自己的处理器记录。 - - ## 处理器 下面用的是 [SDK 指南](/zh-cn/docs/sdk/)里的 `agent`: @@ -43,8 +41,6 @@ agent.events.on(StepFinished, report_step) 这个顺序只管一次派发。并发的工具调用或两个运行,可能同时发事件。处理器里共享的状态,仍要自己协调。 - - ## 调用控制 要叫停一次调用,给事件的 `decision` 字段赋值: @@ -77,12 +73,10 @@ def require_refund_permission(event: ToolCallStarted) -> None: | `ChatFinished` | 这一步的往返可以记录了。 | 读这一次往返。 | | `CommandFinished` | 直接命令完成或失败。 | 读命令、状态和输出。 | -命令提示词直接跑工具,会发出工具事件和 `CommandFinished`。见[命令](/zh-cn/docs/sdk/tools/#commands)。 +命令提示词直接跑工具,会发出工具事件和 `CommandFinished`。见[命令](/zh-cn/docs/sdk/tools/#不经过模型调用工具)。 所有 agent 事件都带本轮的 `state`。从 `TapeEvent` 派生的事件还带当前 tape。工具事件在调用有上下文时,可以从 `event.call.context` 取到。 - - ## 记录 Agent 在你的处理器之前注册 TapeRecorder。它记录 `RunStarted`、`RunFinished`、`StepStarted`、`StepFinished`、`ChatFinished` 和 `CommandFinished`。`ChatFinished` 这个处理器写入消息和工具往返,后面的模型调用就靠它拿上下文。 @@ -91,8 +85,6 @@ Agent 在你的处理器之前注册 TapeRecorder。它记录 `RunStarted`、`Ru 要存自定义事件,加一个处理器把它写进 tape。要改请求或结果,用调用拦截事件。`ChatFinished` 的观察器运行时,默认记录器已经把这次往返存好了。 - - ## 自定义事件 定义一个继承 `AgentEvent` 的 dataclass。处理器需要 tape 时,就继承 `TapeEvent`。下面这个例子记一条业务事件: @@ -122,11 +114,9 @@ async def mark_reviewed() -> None: `emit()` 在处理器跑完后返回事件。自定义的可变事件可以把结果带回发出方。结果一确定就该停止派发时,覆写它的 `stopped` 属性。工具可以通过 `context.events` 发事件,用的是同一份本轮状态。 - - ## 应用插件 -`AgentPlugins` 把四个 hook 接到调用事件上:`before_llm_call`、`after_llm_call`、`before_tool_call`、`after_tool_call`。它把 `continue_prompt` 接到 `StepFinished`,把 steering inbox 接到 `MessagesPending`。插件按 hook 的要求返回值,适配器去改对应的事件。`BubAgent` 用的就是这个适配器,核心 Agent [也可以这么用](/zh-cn/docs/sdk/reuse-plugins/#agent-plugins)。 +`AgentPlugins` 把四个 hook 接到调用事件上:`before_llm_call`、`after_llm_call`、`before_tool_call`、`after_tool_call`。它把 `continue_prompt` 接到 `StepFinished`,把 steering inbox 接到 `MessagesPending`。插件按 hook 的要求返回值,适配器去改对应的事件。`BubAgent` 用的就是这个适配器,核心 Agent [也可以这么用](/zh-cn/docs/sdk/reuse-plugins/#其他-hooks)。 这个适配器遵循插件 hook 的顺序。单个拦截 hook 抛异常时,它接住、记日志、跳过。要拒绝一次调用,就返回对应的 decision。其他应用 hooks 按各自的出错规则处理。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx index 74d0a82a6..03c879bde 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx @@ -20,7 +20,7 @@ description: 判断你的任务需要工具、事件处理器、技能、插件 普通的 Agent 只用你给的工具和提示词片段。Bub 应用会去发现技能,也会读 `AGENTS.md`。见 [技能](/zh-cn/docs/app/skills/)和[工具](/zh-cn/docs/app/tools/)。 -agent 也可以用一条[文本命令](/zh-cn/docs/sdk/tools/#commands)调工具,或者用 CodeMode 让模型写 Python 来调。这几条路走的是同一个工具实现。 +agent 也可以用一条[文本命令](/zh-cn/docs/sdk/tools/#不经过模型调用工具)调工具,或者用 CodeMode 让模型写 Python 来调。这几条路走的是同一个工具实现。 ## 消息与状态 diff --git a/website/src/content/docs/zh-cn/docs/concepts/tape.mdx b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx index 7836cf0d9..4bb036891 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/tape.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx @@ -7,8 +7,6 @@ Agent 需要两样东西。一是自己干过什么的记录,二是下一次 `core.Agent` 和 `BubAgent` 共用这套机制。两者的默认存储和会话名不一样。 - - ## 存储 普通的 `Agent` 默认创建 `InMemoryTapeStore`。历史跟着这个存储一起活,存储没了就没了。要在进程之间保留,传一个文件存储: @@ -45,8 +43,6 @@ async def inspect_history() -> None: `TapeStore` 用同步方法,`AsyncTapeStore` 用异步方法。Agent 会把同步存储适配成异步调用。持久化和跨进程协调,由你的存储和应用负责。 - - ## 上下文 tape 的条目有消息、工具调用、工具结果、事件和 anchor。正常记录只追加。要清空历史,得显式调用 `Tape.reset()` 这类操作。 @@ -77,16 +73,12 @@ async def start_next_phase() -> None: Agent 认出上下文长度错误时,会自动 handoff 一次,然后用更短的上下文重试。要保留前面历史的摘要,就得在 handoff 之前生成。如果剩下的请求还是太大,重试仍会失败。 - - ## 状态 传给 `agent.run()` 的 `state` 是一个可变字典。本轮的工具、提示词片段和事件处理器共用它。持久化由你的应用负责。长期数据放进自己的存储,或者把选定的事实写进 tape,需要时再读回来。 默认的 [TapeRecorder](/zh-cn/docs/concepts/events/#recording)保存会话往返和一部分生命周期记录。其他变量、自定义事件和决策,要自己记录。 - - ## 分支 每次 `agent.run()` 都会给它的 tape 开一个分支。流关闭时,分支把新条目追加到父 tape。抛异常或提前取消也一样。所以一次失败的运行,也可能留下有用的部分历史。 @@ -103,8 +95,6 @@ Agent 认出上下文长度错误时,会自动 handoff 一次,然后用更 Bub 应用给大型工具结果挂了一个 spill sidecar。会话里留一小段预览和一个句柄,agent 可以用 `spill.read` 分段读。合并分支或重置 tape 时,sidecar 的失败单独记录,主 tape 的操作照样完成。spill 的上限在[应用配置](/zh-cn/docs/app/configure/)里。 - - ## 应用存储 `BubAgent` 用的是 `BubFramework` 给的存储。内置应用用 Bub home 目录下的 `FuzzyFileTapeStore`,通常是 `~/.bub/tapes/`。tape 名称由工作区路径和会话 ID 的哈希拼成。所以同一个会话 ID 在两个工作区里,历史是分开的。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/index.mdx b/website/src/content/docs/zh-cn/docs/integrations/index.mdx index 81ca5cf96..2b6a4690c 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/index.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/index.mdx @@ -19,6 +19,6 @@ description: 按使用 Bub 的方式选择集成:MCP 工具、编辑器、本 - [接入本地 llama.cpp 模型](/zh-cn/docs/integrations/local-models/):让应用调用本地模型服务。 - [用 SQLite 存储会话历史](/zh-cn/docs/integrations/storage/):通过插件替换应用的文件存储。 - [可观测性](/zh-cn/docs/integrations/observability/):查看本地记录,或将轨迹导出到 Phoenix 和 Logfire。 -- [WASM 插件](/zh-cn/docs/app/plugins/#wasm-plugins):用 Go 或 Rust 写 hook,通过 WebAssembly 运行。 +- [WASM 插件](/zh-cn/docs/app/plugins/#wasm-插件实验性):用 Go 或 Rust 写 hook,通过 WebAssembly 运行。 各集成指南都提供插件文档链接,可据此查看该插件的具体配置方式。 diff --git a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx index b93c2b51d..98c560a3f 100644 --- a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx @@ -5,7 +5,7 @@ description: Bub 插件的 hook 签名、调用规则和资源生命周期。 Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 个 hooks。用法示例见[Hooks](/zh-cn/docs/app/hooks/),应用阶段顺序见[Turn](/zh-cn/docs/concepts/turn/)。 -下文的“首个”指按 hook 优先级取得第一个非 `None` 的结果,“全部”指调用每个实现。普通 hook 抛出异常时,本次分发停止,异常向上传播;[agent 拦截 hooks](#agent-loop-hooks-pluginagenthooks) 和 `on_error` 有各自的异常处理规则。 +下文的“首个”指按 hook 优先级取得第一个非 `None` 的结果,“全部”指调用每个实现。普通 hook 抛出异常时,本次分发停止,异常向上传播;[agent 拦截 hooks](#通过-pluginagenthooks-拦截调用) 和 `on_error` 有各自的异常处理规则。 ## Turn Hooks @@ -57,8 +57,6 @@ Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 - - ## 优先级与分发 Bub 先注册内置 hooks,再注册已安装插件的入口点。普通实现优先级相同时,后注册的先运行。`@hookimpl(tryfirst=True)` 和 `@hookimpl(trylast=True)` 会改变优先级。这些标记与注册顺序共同决定哪个实现先运行。 @@ -69,16 +67,12 @@ Bub 先注册内置 hooks,再注册已安装插件的入口点。普通实现 `bub hooks` 按 pluggy 内部保存的顺序显示绑定,用于确认注册结果。执行时遵循上面的优先级规则。 - - ## 提示词内容 `build_prompt` 可以返回包含字符串和 Republic 媒体的列表,媒体类型包括 `republic.Image`、`Audio` 和 `Video`。使用 `republic.image(...)`、`republic.audio(...)`、`republic.video(...)` 构造媒体。 旧格式的字符串和内容块字典仍可使用。接收提示词的 hooks(`run_model`、`run_model_stream`、`continue_prompt`、`system_prompt`)收到的是旧格式:没有媒体时是字符串,否则是带媒体 URL 的内容块。内联媒体使用 data URL。`bub.prompt.to_content(prompt)` 可以转换为用户内容。 - - ## 运行时资源 `framework.running()` 先进入 lifespan providers,再获取会话存储和 steering inbox。退出时按相反顺序关闭已经进入的资源,发生错误时也是如此。执行环境缓存也会在作用域退出时关闭。 @@ -91,8 +85,6 @@ Bub 先注册内置 hooks,再注册已安装插件的入口点。普通实现 Tape sidecar providers 提供具名的关联 tape。Bub 把它们挂载到应用的根 Tape 上,fork、merge、archive 和 reset 都会处理已挂载的 sidecars。移除 provider 后,已保存的数据仍然保留。公共存储语义见 [Tape 与上下文](/zh-cn/docs/concepts/tape/)。 - - ## 通过 PluginAgentHooks 拦截调用 @@ -100,7 +92,7 @@ Tape sidecar providers 提供具名的关联 tape。Bub 把它们挂载到应用 Before hooks 按 hook 优先级运行,每个实现都能看到前序实现的更新。`LLMCallDecision.finish(text)`、`ToolCallDecision.replace(result)` 和 `ToolCallDecision.deny(message)` 会停止后续 before 实现。LLM finish decision 不调用供应商,也没有对应的 `after_llm_call`。 -适配器捕获每个拦截 hook 的普通异常,记录 `hook.agent_hook_failed` 后继续。需要拒绝调用时,返回 decision。直接注册到[核心 EventBus](/zh-cn/docs/concepts/events/#delivery) 的 handler 会向上传播异常。 +适配器捕获每个拦截 hook 的普通异常,记录 `hook.agent_hook_failed` 后继续。需要拒绝调用时,返回 decision。直接注册到[核心 EventBus](/zh-cn/docs/concepts/events/#处理器) 的 handler 会向上传播异常。 After hooks 观察成功调用和普通异常失败,取消与消费方关闭不会触发。`after_tool_call` 也会处理拒绝或替换的调用,各个实现共享同一个结果对象。修改 `result.result` 会改变向后传递的值,`result.error` 则保留原始失败信息。 diff --git a/website/src/content/docs/zh-cn/docs/reference/python-api.mdx b/website/src/content/docs/zh-cn/docs/reference/python-api.mdx index 658e779fb..6119de12b 100644 --- a/website/src/content/docs/zh-cn/docs/reference/python-api.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/python-api.mdx @@ -9,8 +9,6 @@ description: 核心 agent 与 Bub 应用的接口,按所属范围分组。 核心循环使用的接口,不读取应用配置。 - - ### 流 ```python @@ -50,7 +48,7 @@ from bub.hooks.interception import ( ) ``` -`BubError.kind` 表示错误类别,`as_dict()` 返回适合传输的载荷。拦截载荷与 `bub.core.events` 共用类型。插件如何使用它们,以及异常如何处理,见 [Hooks](/zh-cn/docs/reference/hooks/#agent-loop-hooks-pluginagenthooks)。 +`BubError.kind` 表示错误类别,`as_dict()` 返回适合传输的载荷。拦截载荷与 `bub.core.events` 共用类型。插件如何使用它们,以及异常如何处理,见 [Hooks](/zh-cn/docs/reference/hooks/#通过-pluginagenthooks-拦截调用)。 ## 应用 @@ -143,7 +141,7 @@ from bub.builtin.agent import BubAgent 应用配置是进程级共享的。需要使用 framework 当前存储时,在 `framework.running()` 内创建 `BubAgent`。显式提供的 store 优先,生命周期仍由调用方管理。两种 store 都没有时,agent 使用内存存储。 -省略 `state` 时,`run_stream` 加载初始状态;传入 `state` 时,则直接使用调用方提供的状态。状态保存 hooks、出站消息处理和并发 turn 调度由宿主管理。完整用法见[嵌入应用](/zh-cn/docs/app/distribution/#programmatic-embedding)。 +省略 `state` 时,`run_stream` 加载初始状态;传入 `state` 时,则直接使用调用方提供的状态。状态保存 hooks、出站消息处理和并发 turn 调度由宿主管理。完整用法见[嵌入应用](/zh-cn/docs/app/distribution/#嵌入应用)。 ## 导入路径 diff --git a/website/src/content/docs/zh-cn/docs/sdk/index.md b/website/src/content/docs/zh-cn/docs/sdk/index.md index 602538913..e2a9edccd 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/index.md +++ b/website/src/content/docs/zh-cn/docs/sdk/index.md @@ -7,8 +7,6 @@ description: 在一个最小的 Python 程序里跑起 Agent,并控制它产 这一页假设你已经按 Agent 的方式装好 Bub,见[安装 Bub](/zh-cn/docs/getting-started/install/#agent-install)。[Bub 应用](/zh-cn/docs/app/workspace/)建立在同一个内核上,额外提供配置和插件发现。 - - ## 运行示例 在项目中保存为 `sdk_example.py`: @@ -64,8 +62,6 @@ uv run python sdk_example.py 使用相同的 `tape` 名称再次运行程序,就会继续 `sessions/` 中保存的会话。换一个名称即可开始独立会话。 - - ## 管理运行与输出流 `agent.run()` 返回一个流,迭代这个流会推进执行:调用模型、执行模型请求的工具,再次调用模型,直到模型回答且不再请求工具。一个 `final` 事件表示一个模型步骤结束;完整消费流,才能完成这次运行。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/options.mdx b/website/src/content/docs/zh-cn/docs/sdk/options.mdx index ae857b1dc..8ae486df8 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/options.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/options.mdx @@ -5,8 +5,6 @@ description: 选择模型、指令,以及影响一次运行的其他选项。 这些选项决定 Agent 怎么跑。先在[基础 Agent](/zh-cn/docs/sdk/)的示例上跑通,再按需修改。 - - ## 选择模型 将 Republic chat model 传给 `Agent(model=...)`,可以设置默认模型;传给 `agent.run(model=...)`,则只用于当前运行。如果运行需要模型,而两处都没有提供,就会抛出 `ValueError`。模型的生命周期由你管理,请用 `async with` 包住使用它的运行过程。 @@ -35,8 +33,6 @@ async with republic.get_model( `LLMCallStarted` 处理器可以更改请求的模型。使用 Republic chat model 时,同一供应商内的切换复用当前客户端;切换到其他供应商会读取 Republic 环境配置创建客户端。需要不同策略时,提供自己的 `ModelClient`。 - - ## 设置指令 `system_prompt` 接收文本、函数,或按顺序排列的两者。函数接收 `PromptContext`,支持同步和异步。Agent 在每轮运行的第一个模型步骤前渲染一次这些片段,忽略空结果,再用空行拼接;同一轮运行的后续步骤复用这段文本。 @@ -54,15 +50,8 @@ def customer(context: PromptContext) -> str | None: `PromptContext` 包含当前运行的 prompt、提供给模型的工具、模型标识、Tape、turn 状态和 Agent。`available_tools` 列出 Agent 注册的工具。使用 `agent.system_prompt.remove(section)` 可以移除片段。 - - - 传给运行的 `state` 是可变字典,由工具、提示词片段和事件处理器共用。哪些值需要保存、下次运行时如何恢复,由你决定,详见[本轮状态](/zh-cn/docs/concepts/tape/#state)。 - - - - ## 设置其他参数 | 参数 | 默认值与用途 | @@ -80,8 +69,6 @@ def customer(context: PromptContext) -> str | None: 传入的 `Tool` 按名称保存在 `agent.tools` 中,供应商工具保存在 `agent.builtin_tools` 中。构造或更新 Agent 时,由你显式选择这些工具。 - - ## 定制步骤准备方式 当工具选择需要自己的业务逻辑时,可以继承 `Agent` 并重写 `prepare_tools(tape, prompt)`。默认提供传入的工具,排除 `exposure="command"`。Agent 在每轮运行的第一个模型步骤前准备一次工具,之后每一步都提供同一组工具;调用不在其中的工具时,会重新执行 `prepare_tools(tape, None)` 来解析,因此运行中新变为可用的工具仍可调用。`tape.context.state` 就是本轮状态。 @@ -111,6 +98,3 @@ def check_results(event: StepFinished) -> None: ``` `agent.session(name, state=...)` 打开一个由你管理生命周期的 Tape。把它返回的 `Tape` 对象(不是名称)传给 `agent.run_on(tape, prompt, model=...)` 或 `agent.run_once(prompt, model=..., tape=)`。`run_on()` 返回的流需要由你关闭;`run_once()` 只产出一个模型步骤,适合自己驱动循环的场景。 - - - diff --git a/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx b/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx index 259274837..437400cf2 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx @@ -5,7 +5,7 @@ description: 将插件提供的工具和 hooks 接入自己的 Agent,保留现 你可以在现有 Python 服务中使用 Bub 应用插件。宿主继续管理 HTTP 路由、任务入口和输出处理;根据要复用的应用行为,选择 Agent 和运行入口。 -`BubAgent` 已经将内核循环、设置、插件 hooks、技能和工具策略组合在一起,它的[直接流入口](#app-runtime)也可以嵌入宿主程序。需要完整消息处理流程时,使用 `BubFramework.process_inbound()`。 +`BubAgent` 已经将内核循环、设置、插件 hooks、技能和工具策略组合在一起,它的[直接流入口](#复用-bubagent)也可以嵌入宿主程序。需要完整消息处理流程时,使用 `BubFramework.process_inbound()`。 使用 `core.Agent` 时,由你选择组件并接入 hooks。下面分别取用搜索插件的工具、适配上下文插件的调用 hooks,每个示例都说明了该插件需要的依赖。后台服务还需要各自的启动和关闭流程。 @@ -109,8 +109,6 @@ Handoff 把摘要保存在锚点上。下一次模型请求需要这份摘要时 适配器会捕获普通插件异常并跳过失败的 hook。需要阻止工具执行时,hook 应返回 `ToolCallDecision.deny(...)`。具体拦截语义见 [Agent 事件](/zh-cn/docs/concepts/events/)。 - - ### 其他 hooks 其余 hooks 决定一次运行中有什么。每个 hook 对应核心 Agent 的一个扩展点: @@ -148,16 +146,14 @@ async with plugins.running(): | 插件能力 | 接入方式 | | --- | --- | | 工具或存储 | 导入公开组件并传给 Agent。例如,`bub_tapestore_sqlalchemy.store.SQLAlchemyTapeStore` 接收数据库 URL,可作为 `store=` 传入。 | -| 模型或工具拦截、系统提示词、继续运行或 steering | 注册指定插件并[接入 `AgentPlugins`](#agent-plugins),同时核对插件需要的状态字段和工具。 | -| 消息准备、模型阶段替换、状态保存或消息发送 | 在宿主中[嵌入应用流程](/zh-cn/docs/app/distribution/#programmatic-embedding),执行这些阶段并取得 `TurnResult`。 | +| 模型或工具拦截、系统提示词、继续运行或 steering | 注册指定插件并[接入 `AgentPlugins`](#其他-hooks),同时核对插件需要的状态字段和工具。 | +| 消息准备、模型阶段替换、状态保存或消息发送 | 在宿主中[嵌入应用流程](/zh-cn/docs/app/distribution/#嵌入应用),执行这些阶段并取得 `TurnResult`。 | | 后台连接或动态发现的工具 | 启动服务、等待就绪、绑定工具,并随宿主关闭。具体顺序遵循插件自己的生命周期接口。 | 按所选存储的生命周期约定管理资源。`framework.running()` 会获取 hooks 提供的资源,并在退出时关闭它进入的上下文管理器。返回普通对象的存储仍需遵循自己的清理约定。 接入 MCP 时,按照插件的[嵌入示例](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-mcp#embedding),用 `MCPChannel.connect()` 建立连接,通过 `bind_agent()` 把工具绑定到 `BubAgent`,最后调用 `stop()` 关闭连接。宿主在使用 `framework.running()` 之外,还需要管理这段生命周期。 - - ## 复用 BubAgent `BubAgent` 在内核循环上接入 Bub 设置、技能、工具策略和插件 hooks。宿主可以通过 `framework.load_hooks()` 加载已安装插件,进入 `framework.running()` 后构造 `BubAgent(framework)`。它的 `run_stream(*, session_id, prompt, state=None, ...)` 返回由宿主消费和关闭的流。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx index 8521651b2..bb593d08f 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx @@ -7,8 +7,6 @@ description: 定义一个动作,再直接交给 Agent,或通过 CodeMode 从 本页说明共同的工具接口和 SDK 接入方式。安装步骤与完整的模型循环示例见[构建自己的 Agent](/zh-cn/docs/sdk/)。 - - ## 定义工具 `Tool.from_callable()` 从类型注解生成参数定义,从 docstring 读取描述。它支持同步和异步函数,返回的工具由你传给 Agent。 @@ -29,9 +27,7 @@ agent = Agent(tools=[add_tool]) 运行时保留 `math.add` 这个名称;提供给模型时,点会换成下划线,因此模型看到的是 `math_add`。也可以通过 `description=` 显式提供描述。 -普通 `Agent` 会把传入的工具提供给模型,仅排除 `exposure="command"`。延迟加载、`allowed_tools` 状态和 `state["code_mode"]` 开关属于 [BubAgent 的应用策略](/zh-cn/docs/app/tools/)。在自己的 Agent 中按轮次选择工具,可以覆写 [`prepare_tools()`](/zh-cn/docs/sdk/options/#customize-the-loop)。 - - +普通 `Agent` 会把传入的工具提供给模型,仅排除 `exposure="command"`。延迟加载、`allowed_tools` 状态和 `state["code_mode"]` 开关属于 [BubAgent 的应用策略](/zh-cn/docs/app/tools/)。在自己的 Agent 中按轮次选择工具,可以覆写 [`prepare_tools()`](/zh-cn/docs/sdk/options/#定制步骤准备方式)。 ## 返回可用数据 @@ -59,9 +55,7 @@ order_tool = Tool.from_callable( `Tool.render(result)` 使用这个 renderer。默认情况下,字符串直接保留,Pydantic 模型与可序列化的值转为 JSON,其他值使用 `str()`。面向模型的执行器在 `ToolCallFinished` 之前渲染结果,因此处理器看到的是将交给模型的内容。CodeMode 使用的执行器会保留结构化结果。 -在模型循环中,工具执行错误会作为工具结果交回模型,让模型决定如何回答或重试。直接通过命令调用时,错误会传播给调用方。[事件处理器](/zh-cn/docs/concepts/events/#delivery)有自己的异常规则。 - - +在模型循环中,工具执行错误会作为工具结果交回模型,让模型决定如何回答或重试。直接通过命令调用时,错误会传播给调用方。[事件处理器](/zh-cn/docs/concepts/events/#处理器)有自己的异常规则。 ## 访问当前会话 @@ -82,8 +76,6 @@ remember_tool = Tool.from_callable(remember, context=True) 这会在 Tape 中写入一条业务事件。默认上下文选择器读取会话消息与工具交互;要把这条事件交给模型,需要提供自己的选择逻辑。哪些已保存的信息应交给模型,由[Tape](/zh-cn/docs/concepts/tape/#context)中的选择规则决定。 - - ## 组合工具与 CodeMode CodeMode 让模型编写 Python 来调用工具。它提供 `run_code` 工具,以及描述代码中可用工具的提示词片段。使用普通 Agent 时,需要显式加入这两部分: @@ -101,7 +93,7 @@ agent = Agent( ) ``` -按 [SDK 指南](/zh-cn/docs/sdk/options/#models)中的方式管理模型并运行 Agent。模型可以向 `run_code` 传入这样的代码: +按 [SDK 指南](/zh-cn/docs/sdk/options/#选择模型)中的方式管理模型并运行 Agent。模型可以向 `run_code` 传入这样的代码: ```python order = await tools.lookup_order(order_id="A123") @@ -125,8 +117,6 @@ print(order["status"]) `run_code` 返回打印的输出和附带媒体,默认超时为 120 秒,可以通过 `timeout_seconds` 调整。使用 `LocalEnvironment` 时,每次调用都会启动一个拥有宿主用户权限的 Python 进程,在完成或取消时停止它及其子进程。如果需要不同的执行边界,请选择其他环境。 - - ## 选择执行环境 `bub.environment` 中的 `Environment` 接口提供进程执行、文件访问和 Python 执行能力。`CodeMode(environment=...)` 使用你传入的环境。在 Bub 应用中,[`provide_environment` hook](/zh-cn/docs/reference/hooks/)负责为会话选择环境。 @@ -141,8 +131,6 @@ print(order["status"]) 环境执行生成的代码,通过回调交给宿主执行你的 Python 工具。应用不再使用自定义环境时,需要关闭它。 - - ## 不经过模型调用工具 设置 `command_prefix` 后,就可以接收文本命令。下面使用前面定义的 `add_tool`,不需要模型凭据: From a24e54af6c6cecd9a5e2185a1db4040ed37ed330 Mon Sep 17 00:00:00 2001 From: 100gle Date: Thu, 22 Oct 2026 15:27:22 +0800 Subject: [PATCH 14/28] docs(getting-started): show the layers and design principles as cards Turn the core/app layer bullets and the four design principles into Card/CardGrid blocks so the page reads less flat, in both en and zh-cn. --- .../docs/docs/getting-started/index.mdx | 33 +++++++++++++---- .../docs/zh-cn/docs/getting-started/index.mdx | 35 ++++++++++++++----- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/website/src/content/docs/docs/getting-started/index.mdx b/website/src/content/docs/docs/getting-started/index.mdx index 0aa594251..535a1f723 100644 --- a/website/src/content/docs/docs/getting-started/index.mdx +++ b/website/src/content/docs/docs/getting-started/index.mdx @@ -2,7 +2,7 @@ title: What is Bub? description: A lightweight agent runtime written in Python. Run it as-is, or embed it in your own program. --- -import { Aside } from '@astrojs/starlight/components'; +import { Aside, Card, CardGrid } from '@astrojs/starlight/components'; Bub is a lightweight agent runtime written in Python. It ships a ready-to-run agent application, and it lets you replace the execution logic and compose plugins as you see fit. @@ -10,9 +10,18 @@ An agent is at heart a loop: the model reads the context and decides which tool So an agent rests on four things: a **model**, **tools**, a **context**, and the **loop** that drives them. Working from that shared understanding, Bub keeps a small core and builds a ready-to-run application around it: -- **The core layer**: composable agent capabilities such as the loop, tools, context, the event bus, and the model client. It calls models through [republic](https://getrepublic.org) and depends only on [pydantic](https://pydantic.dev) and [loguru](https://github.com/delgan/loguru). + + + Composable agent capabilities: the loop, tools, context, the event bus, and the model client. -- **The app layer**: a ready-to-run harness — settings, plugins, hooks, skills, message channels, a gateway, and more. None of it needs to be built yourself; a little configuration is enough to start. + It calls models through [republic](https://getrepublic.org) and depends only on [pydantic](https://pydantic.dev) and [loguru](https://github.com/delgan/loguru). + + + A ready-to-run harness: settings, plugins, hooks, skills, message channels, a gateway, and more. + + None of it needs to be built yourself; a little configuration is enough to start. + + Which one should you use? @@ -27,10 +36,20 @@ Which one should you use? Bub is built on being simple, composable, and extensible. That means you can freely combine Bub's capabilities, or build your own agent on top of it: -1. **Context is append-only.** Each turn appends to the tape and never rewrites an entry. The model reads a slice of the tape, not a message array that grows without limit. -2. **Every step reports an event.** Model calls and tool calls each report a start, an end, and a result. A host can watch a run, change the next input, or stop it. -3. **A tool is a function.** A Python function with type hints is enough. Code mode lets the model write a short program that calls several tools in one step. -4. **Everything is a plugin.** Every capability in Bub can come from a plugin. A plugin can be a tool, an event listener, a message channel, or a whole set of skills. + + + Each turn appends to the tape and never rewrites an entry. The model reads a slice of the tape, not a message array that grows without limit. + + + Model calls and tool calls each report a start, an end, and a result. A host can watch a run, change the next input, or stop it. + + + A Python function with type hints is enough. Code mode lets the model write a short program that calls several tools in one step. + + + Every capability in Bub can come from a plugin. A plugin can be a tool, an event listener, a message channel, or a whole set of skills. + + The [concepts](/docs/concepts/) section defines all of this in full. diff --git a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx index 8ebed02a5..454f5d2ba 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx @@ -2,17 +2,26 @@ title: Bub 是什么? description: 一个由 Python 构建的轻量级 Agent 运行时。可以直接运行,也可以嵌入你自己的程序。 --- -import { Aside } from '@astrojs/starlight/components'; +import { Aside, Card, CardGrid } from '@astrojs/starlight/components'; Bub 是一个由 Python 构建的轻量级 Agent 运行时。它自带一个开箱即用的 Agent 应用,也可以按需替换执行逻辑、自由组合插件。 Agent 的本质是一个循环:模型读取上下文并决定调用哪个工具,工具的结果写回上下文,再交给模型;当模型不再请求工具、直接给出答案时,循环结束。 -构成一个 Agent 的核心有四个:**模型**、**工具**、**上下文**,以及驱动它们的**循环**。所以在这样的共识下 Bub 设计了精简的内核,并围绕内核构建出了一个开箱即用的应用: +构成一个 Agent 的核心有四个:**模型**、**工具**、**上下文**,以及驱动它们的**循环**。所以在这样的共识下 Bub 设计了精简的内核,并围绕内核构建出了一个开箱即用的应用。 -- **核心层**:提供可自由组合的 Agent 能力,如循环、工具、上下文、事件总线、模型客户端等,它通过 [republic](https://getrepublic.org) 调用模型,仅依赖 [pydantic](https://pydantic.dev) 与 [loguru](https://github.com/delgan/loguru)。 + + + 提供可自由组合的 Agent 能力:循环、工具、上下文、事件总线、模型客户端。 -- **应用层**:提供了开箱即用的 Harness,如配置、插件、hooks、技能、消息渠道、网关等,这些能力都无需你去一一实现,使用时只需简单配置即可开始使用。 + 通过 [republic](https://getrepublic.org) 调用模型,仅依赖 [pydantic](https://pydantic.dev) 与 [loguru](https://github.com/delgan/loguru)。 + + + 在核心层之上给出开箱即用的 Harness:配置、插件、hooks、技能、消息渠道、网关。 + + 这些都不需要你从头实现,配好就能开始用。 + + 如何选择? @@ -27,10 +36,20 @@ Agent 的本质是一个循环:模型读取上下文并决定调用哪个工 Bub 秉持着简单、可组合、可扩展的设计理念,这意味着你可以自由组合 Bub 的能力,或在 Bub 的基础上构建自己的 Agent: -1. **追加式上下文**:每个 turn 追加写入 tape,不修改已有条目。模型读的是 tape 的一个切片,而不是一个不断增长的 message 数组。 -2. **事件驱动**:模型调用与工具调用各自发出开始、结束和结果。宿主可以旁观一次运行、改写下一步的输入,或直接中止。 -3. **工具即函数**:一个有类型标注的 Python 函数即可成为工具。Code mode 让模型自己写一小段程序,在一步之内调用多个工具。 -4. **插件化**:Bub 的能力都可以通过插件提供。插件可以是一个工具、一个事件监听器、一个消息渠道,或一个完整的技能集合。 + + + 每个 turn 追加写入 tape,不修改已有条目。模型读的是 tape 的一个切片,而不是一个不断增长的 message 数组。 + + + 模型调用与工具调用各自发出开始、结束和结果。宿主可以旁观一次运行、改写下一步的输入,或直接中止。 + + + 一个有类型标注的 Python 函数即可成为工具。Code mode 让模型自己写一小段程序,在一步之内调用多个工具。 + + + Bub 的能力都可以通过插件提供。插件可以是一个工具、一个事件监听器、一个消息渠道,或一个完整的技能集合。 + + 完整定义见[核心概念](/zh-cn/docs/concepts/)。 From 0d6bd9ec2d85bff18313f842f0dcdbd001088221 Mon Sep 17 00:00:00 2001 From: 100gle Date: Thu, 22 Oct 2026 15:59:00 +0800 Subject: [PATCH 15/28] docs(getting-started): split the quick start into CLI and SDK walks Replace the tabbed layout with two standalone sections and render each walk as a Steps list. Sync the English page with the rewritten zh-cn one and use kbd for the Ctrl+C shortcut. --- .../docs/docs/getting-started/quickstart.mdx | 117 ++++++++++-------- .../zh-cn/docs/getting-started/quickstart.mdx | 117 ++++++++++-------- 2 files changed, 124 insertions(+), 110 deletions(-) diff --git a/website/src/content/docs/docs/getting-started/quickstart.mdx b/website/src/content/docs/docs/getting-started/quickstart.mdx index 5ff978aa2..45ee15c15 100644 --- a/website/src/content/docs/docs/getting-started/quickstart.mdx +++ b/website/src/content/docs/docs/getting-started/quickstart.mdx @@ -1,97 +1,104 @@ --- title: Quick start -description: Run Bub in your terminal, or embed it in a Python program. +description: Get up and running with Bub in your terminal or in your own Python project. --- -import { CardGrid, LinkCard, Tabs, TabItem } from '@astrojs/starlight/components'; +import { CardGrid, LinkCard, Steps } from '@astrojs/starlight/components'; -Bub runs in two places: as an application in its own process, or as a library inside your Python program. Both run the same kernel, so you can move between them later. +Since 0.6.0, Bub itself is split in two, so you can pick whichever part fits how you want to use it. This page assumes [Bub is installed](/docs/getting-started/install/). -## Run it +## Start from the CLI - - +Open your terminal and work through the following steps on the command line: -Configure a model once, then start a conversation in any project directory: + -```bash -bub onboard -``` +1. (Optional) If you have never configured Bub, run this command first to finish the initial setup: -```bash -mkdir hello-bub && cd hello-bub -bub chat -``` + ```bash + bub onboard + ``` -Ask it something: +2. Create a workspace (a directory) anywhere and switch into it, then launch an interactive session with `bub chat`: -```text -> Summarize the files in this directory -``` + ```bash + mkdir hello-bub && cd hello-bub + bub chat + ``` -Bub reads `AGENTS.md` when the directory has one, records the turn on its tape, and answers with the model you configured. Press `Ctrl+C` to leave. +3. Now try copying and pasting the text below to have a conversation with Bub: -Next: [Workspace](/docs/app/workspace/) covers `AGENTS.md` and the commands of the app. + ```shell + Summarize the files in this directory + ``` - - + -Add Bub to a project and drive the loop yourself: +Easy enough, right? Press Ctrl+C to leave the interactive session, then run `bub --help` to see more commands and options, or [read the docs](/docs/reference/cli) to learn more. -```bash -uv init --python 3.12 my-agent && cd my-agent -uv add bub -``` +## Run it with the SDK -Save this as `main.py`: +A Python craftsperson may want to write some code, so here is how to try the Bub SDK inside a Python project. -```python -import asyncio + -import republic +1. Create a new project and install Bub: -from bub.core import Agent + ```bash + uv init --python 3.12 my-agent && cd my-agent + uv add bub + ``` +2. Save the following code as `main.py`: -async def main() -> None: - agent = Agent(system_prompt="Be concise.") - async with republic.get_model("openai:gpt-5-mini") as model: - async with agent.run("What can you do?", model=model, tape="demo") as stream: - async for event in stream: - if event.kind == "text": - print(event.data.get("delta", ""), end="") + ```python + import asyncio + import republic -asyncio.run(main()) -``` + from bub.core import Agent -```bash -export REPUBLIC_OPENAI_API_KEY=sk-... -uv run python main.py -``` -`Agent` owns the model and tool loop; you pass the model, the tools, and the tape name. The stream reports `text` for the answer, plus `tool_call` and `tool_result` when the model calls a tool. + async def main() -> None: + agent = Agent(system_prompt="Be concise.") + async with republic.get_model("openai:gpt-5-mini") as model: + async with agent.run("What can you do?", model=model, tape="demo") as stream: + async for event in stream: + if event.kind == "text": + print(event.data.get("delta", ""), end="") -Next: [Build your own Bub Agent](/docs/sdk/) adds a tool and explains the stream lifecycle. - - + asyncio.run(main()) + ``` -## Where each path goes +3. Run it with your API key: + + ```bash + export REPUBLIC_OPENAI_API_KEY=sk-... + uv run python main.py + ``` + + + +Not sure which APIs the code uses? The [API reference](/docs/reference/python-api/) has more. + +## Next steps + +Congrats — you know the basics of Bub. From here you can explore either path more deeply to unlock more use cases. -Concepts are optional. Every page says when it is worth reading, so you can skip them and come back when a task needs them. +And if you like, you can also read up on the design ideas behind Bub. diff --git a/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx b/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx index 85900f1fb..4017120e7 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/quickstart.mdx @@ -1,97 +1,104 @@ --- title: 快速上手 -description: 在终端里运行 Bub,或把它嵌进 Python 程序。 +description: 学会如何快速开始在终端或在自己的 Python 项目中使用 Bub。 --- -import { CardGrid, LinkCard, Tabs, TabItem } from '@astrojs/starlight/components'; +import { CardGrid, LinkCard, Steps } from '@astrojs/starlight/components'; -Bub 有两种跑法:作为独立进程的应用,或作为 Python 程序里的库。两者跑的是同一套内核,之后可以随时换。 +自 0.6.0 版本起,Bub 本体拆分成了两部分,因此你可以根据需要选择使用哪部分来玩转 Bub。 这一页假设你已经[安装 Bub](/zh-cn/docs/getting-started/install/)。 -## 跑起来 +## 从 CLI 启动 - - +打开你系统的终端,然后按如下步骤在命令行中进行操作: -先配置一次模型,然后在任意项目目录里开始对话: + -```bash -bub onboard -``` +1. (可选)如果你从未配置过 Bub,可以先执行如下命令来完成初始配置: -```bash -mkdir hello-bub && cd hello-bub -bub chat -``` + ```bash + bub onboard + ``` -问它一个问题: +2. 在任意目录中创建一个工作区(目录)并切换到当中,然后使用 `bub chat` 启动一个交互式会话: -```text -> Summarize the files in this directory -``` + ```bash + mkdir hello-bub && cd hello-bub + bub chat + ``` -目录里有 `AGENTS.md` 时 Bub 会读取它,把这次 turn 记到 tape 上,再用你配置的模型回答。按 `Ctrl+C` 退出。 +3. 现在,可以尝试复制粘贴如下文本并与 Bub 进行对话: -下一步:[工作区](/zh-cn/docs/app/workspace/)讲 `AGENTS.md` 和应用的命令。 + ```shell + Summarize the files in this directory + ``` - - + -在自己的项目里添加 Bub,然后自己驱动循环: +一切都很简单对吧?现在你可以使用 Ctrl+C 退出 Bub 交互式会话,然后可以输入 `bub --help` 查看更多命令和选项,或是[查阅文档](/zh-cn/docs/reference/cli)以了解更多信息。 -```bash -uv init --python 3.12 my-agent && cd my-agent -uv add bub -``` +## 用 SDK 运行 -把这段保存为 `main.py`: +作为 Python 工匠,也许你想写点代码,那么可以按以下方式来尝试在 Python 项目中使用 Bub SDK。 -```python -import asyncio + -import republic +1. 创建新项目并安装 Bub: -from bub.core import Agent + ```bash + uv init --python 3.12 my-agent && cd my-agent + uv add bub + ``` +2. 把下面这段代码保存为 `main.py` -async def main() -> None: - agent = Agent(system_prompt="Be concise.") - async with republic.get_model("openai:gpt-5-mini") as model: - async with agent.run("What can you do?", model=model, tape="demo") as stream: - async for event in stream: - if event.kind == "text": - print(event.data.get("delta", ""), end="") + ```python + import asyncio + import republic -asyncio.run(main()) -``` + from bub.core import Agent -```bash -export REPUBLIC_OPENAI_API_KEY=sk-... -uv run python main.py -``` -`Agent` 负责模型与工具循环;模型、工具和 tape 名字由你传入。事件流里 `text` 是回答,模型调用工具时还会有 `tool_call` 和 `tool_result`。 + async def main() -> None: + agent = Agent(system_prompt="Be concise.") + async with republic.get_model("openai:gpt-5-mini") as model: + async with agent.run("What can you do?", model=model, tape="demo") as stream: + async for event in stream: + if event.kind == "text": + print(event.data.get("delta", ""), end="") -下一步:[构建你的 Bub Agent](/zh-cn/docs/sdk/)会加一个工具,并讲清事件流的生命周期。 - - + asyncio.run(main()) + ``` -## 两条路各自通向哪里 +3. 用你的 API Key 跑起来 + + ```bash + export REPUBLIC_OPENAI_API_KEY=sk-... + uv run python main.py + ``` + + + +不知道代码里面有哪些 API?没关系,你可以在[API 文档](/zh-cn/docs/reference/python-api/)中找到更多 API 参考。 + +## 下一步 + +恭喜你,已经掌握 Bub 的基本使用方式了,接下来你可以继续深入探索这两种方式以解锁更多使用场景。 -概念部分可以跳过。每页都会写明什么时候值得读,你可以在遇到具体问题时再回头。 +当然,如果你愿意的话,也可以继续了解一下 Bub 有哪些设计巧思。 From 95450405d8be5addbd8480b6369bc06217a98601 Mon Sep 17 00:00:00 2001 From: 100gle Date: Thu, 22 Oct 2026 16:28:44 +0800 Subject: [PATCH 16/28] docs: give linked headings a stable anchor Starlight derives a heading id from its text, so a section had a different anchor in every locale: `## Storage` became `#storage` while a Chinese heading became a CJK slug. Cross-page links were written against one of them, and the other half silently pointed nowhere. Enable Satteri heading attributes and give the headings other pages link to an explicit id, so both locales share one anchor. This fixes the seventeen broken anchor links across the concepts, SDK, reference, app, and integration pages. --- website/astro.config.mjs | 3 ++ website/src/content/docs/docs/app/hooks.mdx | 12 +++---- .../docs/docs/getting-started/install.mdx | 6 ++-- .../docs/docs/integrations/observability.mdx | 2 +- .../src/content/docs/docs/reference/hooks.mdx | 14 ++++---- .../content/docs/docs/sdk/reuse-plugins.mdx | 14 ++++---- website/src/content/docs/docs/sdk/tools.mdx | 12 +++---- .../src/content/docs/zh-cn/docs/app/hooks.mdx | 14 ++++---- .../content/docs/zh-cn/docs/app/plugins.mdx | 2 +- .../src/content/docs/zh-cn/docs/app/tools.mdx | 10 +++--- .../docs/zh-cn/docs/getting-started/index.mdx | 2 +- .../zh-cn/docs/getting-started/install.mdx | 6 ++-- .../zh-cn/docs/integrations/observability.mdx | 2 +- .../docs/zh-cn/docs/reference/hooks.mdx | 16 +++++----- .../docs/zh-cn/docs/reference/python-api.mdx | 2 +- .../docs/zh-cn/docs/sdk/reuse-plugins.mdx | 14 ++++---- .../src/content/docs/zh-cn/docs/sdk/tools.mdx | 14 ++++---- website/src/integrations/heading-anchors.mjs | 32 +++++++++++++++++++ 18 files changed, 106 insertions(+), 71 deletions(-) create mode 100644 website/src/integrations/heading-anchors.mjs diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 1dc9fe868..0393dfc87 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -4,6 +4,7 @@ import { defineConfig, envField } from 'astro/config'; import tailwindcss from '@tailwindcss/vite'; import starlight from '@astrojs/starlight'; import cloudflare from '@astrojs/cloudflare'; +import headingAnchors from './src/integrations/heading-anchors.mjs'; import peleDiagrams from './src/integrations/pele.mjs'; const astro_image_mode = @@ -54,6 +55,8 @@ export default defineConfig({ }, }, integrations: [ + // Let headings carry an explicit id so every locale shares the same anchor. + headingAnchors(), // Transform Mermaid fences before Starlight highlights code blocks. peleDiagrams(), starlight({ diff --git a/website/src/content/docs/docs/app/hooks.mdx b/website/src/content/docs/docs/app/hooks.mdx index 7ce5e889d..4a004329c 100644 --- a/website/src/content/docs/docs/app/hooks.mdx +++ b/website/src/content/docs/docs/app/hooks.mdx @@ -9,7 +9,7 @@ These examples belong in a [plugin](/docs/app/plugins/). Module-level functions The [hooks reference](/docs/reference/hooks/) defines signatures, priority, and lifecycle rules. -## Change the prompt +## Change the prompt {#change-the-prompt} Use `build_prompt` to replace the incoming content: @@ -36,7 +36,7 @@ def system_prompt(prompt, state): return "Explain which files support your answer." ``` -## Control a model or tool call +## Control a model or tool call {#control-a-model-or-tool-call} Use the agent interception hooks when you want to retain the builtin agent and change an individual call. For example, cap a request's output tokens: @@ -70,7 +70,7 @@ Your authorization logic sets `can_cancel_orders`; the hook uses that value to a An exception in these interception hooks is logged and skipped. Use `deny`, `replace`, or `LLMCallDecision.finish` for control. The [interception contract](/docs/reference/hooks/#interception-through-pluginagenthooks) explains when a decision stops the chain. -## Observe completed calls +## Observe completed calls {#observe-completed-calls} For call metrics, use the completion hooks: @@ -92,7 +92,7 @@ These hooks observe completed calls, including ordinary failures. They do not re To replace the entire model stage, implement either `run_model` or `run_model_stream`. [Your first plugin](/docs/app/plugins/) shows a text replacement. A replacement owns its execution and recording behavior; the builtin agent's interception hooks run only when that agent is used. -## Keep application state across turns +## Keep application state across turns {#keep-application-state-across-turns} The framework builds a fresh state dictionary for each inbound turn. Use `load_state` and `save_state` to carry your application's own values between turns: @@ -120,7 +120,7 @@ This in-memory example counts model-stage exits, including failures. It resets w `save_state` runs in the model stage's `finally` block, after session, state, and prompt preparation have succeeded. Errors during that preparation skip the save hook. Conversation history has a separate [Tape lifecycle](/docs/concepts/tape/#state). -## Provide a tape store +## Provide a tape store {#provide-a-tape-store} Return the store you want the application to use: @@ -158,7 +158,7 @@ def provide_tape_sidecar(): The [shared Tape explanation](/docs/concepts/tape/) describes sidecar naming and merge behavior. -## Add an application command +## Add an application command {#add-an-application-command} `register_cli_commands` receives the root Typer app: diff --git a/website/src/content/docs/docs/getting-started/install.mdx b/website/src/content/docs/docs/getting-started/install.mdx index 1e86edafe..fcd759bbf 100644 --- a/website/src/content/docs/docs/getting-started/install.mdx +++ b/website/src/content/docs/docs/getting-started/install.mdx @@ -5,7 +5,7 @@ description: Install Bub step by step, with several install paths to choose from import { Tabs, TabItem, Aside } from '@astrojs/starlight/components'; -## Install the CLI app +## Install the CLI app {#app-install} @@ -40,7 +40,7 @@ When it finishes, run `bub --help`, then run `bub onboard` to start configuring, If the command is not found, check whether the environment variables include the directory that holds the executable, or add `~/.local/bin` to PATH yourself. -## Install the SDK +## Install the SDK {#agent-install} From 0.6.0 on, Bub ships the core only. Use your package manager to add Bub to your project as a dependency. Here is uv: @@ -49,7 +49,7 @@ uv add bub uv run python -c "from bub.core import Agent; print(Agent.__name__)" ``` -## Run from source +## Run from source {#from-source} Clone the repository and sync its dependencies: diff --git a/website/src/content/docs/docs/integrations/observability.mdx b/website/src/content/docs/docs/integrations/observability.mdx index 23cd71a5b..ab5842de6 100644 --- a/website/src/content/docs/docs/integrations/observability.mdx +++ b/website/src/content/docs/docs/integrations/observability.mdx @@ -14,7 +14,7 @@ You need: - Bub installed and runnable with `bub --help`. - One workspace where `bub run "What tools do you have?"` can call your configured model. - Docker or Podman if you want to run Phoenix locally. -- An activated [application environment](/docs/getting-started/install/#app-development) if you will add tracing dependencies. +- An activated [application environment](/docs/getting-started/install/#app-install) if you will add tracing dependencies. ## 1. Ask Bub for its current tape diff --git a/website/src/content/docs/docs/reference/hooks.mdx b/website/src/content/docs/docs/reference/hooks.mdx index 678aa8fbe..cd8070d71 100644 --- a/website/src/content/docs/docs/reference/hooks.mdx +++ b/website/src/content/docs/docs/reference/hooks.mdx @@ -7,7 +7,7 @@ Hooks connect plugins to the Bub application. This reference lists the 26 hooks `first` below means the first non-`None` result in hook priority order. `all` means every implementation is called. An ordinary hook exception stops that dispatch and propagates; the [agent interception hooks](#interception-through-pluginagenthooks) and `on_error` have separate exception handling. -## Turn hooks +## Turn hooks {#turn-hooks} | Hook | Signature | Behavior | | --- | --- | --- | @@ -25,7 +25,7 @@ These hooks may be synchronous or asynchronous. `continue_prompt` receives a `St The model adapter chooses between text and stream hooks by examining plugins in reverse registration order. It then dispatches the selected hook name by hook priority. It can collect a stream into text or wrap a text response in a stream. Choose one model hook per plugin. -## Application resources and configuration +## Application resources and configuration {#application-resources-and-configuration} | Hook | Signature | Behavior | | --- | --- | --- | @@ -46,7 +46,7 @@ The model adapter chooses between text and stream hooks by examining plugins in Synchronous dispatch skips awaitable return values with a `hook.async_not_supported` warning. Resource providers can return sync or async generators, which Bub enters as context managers. -## Agent interception hooks +## Agent interception hooks {#agent-interception-hooks} | Hook | Signature | Behavior | | --- | --- | --- | @@ -57,7 +57,7 @@ Synchronous dispatch skips awaitable return values with a `hook.async_not_suppor -## Priority and dispatch +## Priority and dispatch {#priority-and-dispatch} Bub registers builtin hooks first, then installed entry-point plugins. For ordinary implementations at the same priority, later registrations run first. `@hookimpl(tryfirst=True)` and `@hookimpl(trylast=True)` alter priority. These flags and registration order together determine which implementation runs first. @@ -67,13 +67,13 @@ Bub registers builtin hooks first, then installed entry-point plugins. For ordin `bub hooks` confirms registration by displaying bindings in pluggy's stored order. Execution follows the priority rules above. -## Prompt content +## Prompt content {#prompt-content} `build_prompt` can return a list containing strings and Republic media: `republic.Image`, `Audio`, or `Video`. Build media with `republic.image(...)`, `republic.audio(...)`, and `republic.video(...)`. Legacy strings and content-block dictionaries remain accepted. Hooks receiving a prompt (`run_model`, `run_model_stream`, `continue_prompt`, `system_prompt`) receive the legacy form: a string without media, otherwise content blocks with media URLs. Inline media uses data URLs. `bub.prompt.to_content(prompt)` converts this to user content. -## Runtime resources +## Runtime resources {#runtime-resources} `framework.running()` enters lifespan providers, then acquires the tape store and steering inbox. It closes entered resources in reverse order, including after an error. The environment cache is also closed on scope exit. @@ -85,7 +85,7 @@ The builtin lifespan owns background shell processes started within that scope. Tape sidecar providers contribute named sibling tapes. Bub mounts them on the application's root Tape and applies fork, merge, archive, and reset to mounted sidecars. Stored data remains after a provider is removed. See [Tape and context](/docs/concepts/tape/) for shared storage semantics. -## Interception through PluginAgentHooks +## Interception through PluginAgentHooks {#interception-through-pluginagenthooks} `AgentPlugins.attach()` subscribes `PluginAgentHooks` to the core agent's LLM and tool start/finish events; `BubAgent` attaches it to itself. This adapter gives application plugins access to those calls. diff --git a/website/src/content/docs/docs/sdk/reuse-plugins.mdx b/website/src/content/docs/docs/sdk/reuse-plugins.mdx index 0b8d7ceaa..aedec950b 100644 --- a/website/src/content/docs/docs/sdk/reuse-plugins.mdx +++ b/website/src/content/docs/docs/sdk/reuse-plugins.mdx @@ -9,7 +9,7 @@ Use Bub's application plugins in your existing Python service. Your host keeps i With `core.Agent`, you choose the components and connect the hooks yourself. The examples below take a search tool from one plugin and adapt the call hooks of another. Each example covers that plugin's dependencies; background services need their own startup and shutdown. -## Add a search tool +## Add a search tool {#add-a-search-tool} [`bub-web-search`](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-web-search) exposes a search tool that works with `core.Agent`. Install it in the environment that runs your service. These examples use plugin packages from the `dev` branch of `bub-contrib`: @@ -68,11 +68,11 @@ For an existing Agent, add the returned tool with `agent.tools[search.name] = se `register_tools()` also updates the process-wide tool registry, so do this setup during startup. An Agent uses the tools you explicitly pass or add to it. This example takes the returned search tool; add the package's other tools, such as Jina's `web.read`, separately if needed. -## Extend the run +## Extend the run {#extend-the-run} `AgentPlugins` connects a plugin's hooks to your Agent's events. -### Model and tool calls +### Model and tool calls {#model-and-tool-calls} [`bub-context-budget`](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-context-budget) runs before and after model calls. When its estimate crosses a threshold, it adds an instruction asking the model to call `tape.handoff`. Give the Agent that tool and subscribe the plugin's hooks to its events. @@ -109,7 +109,7 @@ Register and attach once during service startup. This plugin runs entirely throu The adapter catches ordinary plugin exceptions and skips the failed hook. A hook that blocks a tool must return `ToolCallDecision.deny(...)`; see [Agent events](/docs/concepts/events/) for interception semantics. -### Other hooks +### Other hooks {#other-hooks} The other hooks decide what the run has. Each hook maps to a core Agent extension point: @@ -139,7 +139,7 @@ async with plugins.running(): Message-pipeline hooks, such as `build_prompt`, `run_model`, `save_state`, `render_outbound`, and `dispatch_outbound`, are not part of the agent loop and only run in `process_inbound()`. `plugins=[...]` registers exactly the listed plugins. When several plugins implement the same hook, all of them run: duplicate tools, stores, and tape contexts keep the highest-priority provider, and prompt sections join from low to high priority. `AgentPlugins()` without arguments loads Bub's builtin hooks and the installed plugins, as the `bub` command does. Pass `framework` to use an existing framework's hooks; `plugins` given with it are registered on it. -## Choose how to connect +## Choose how to connect {#choose-how-to-connect} The plugin's implementation determines what your host must provide: @@ -154,7 +154,7 @@ Follow the chosen store's lifecycle contract. `framework.running()` acquires hoo For MCP, follow the plugin's [embedding example](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-mcp#embedding): connect with `MCPChannel.connect()`, attach tools to a `BubAgent` with `bind_agent()`, and close connections with `stop()`. Your host must manage this lifecycle in addition to `framework.running()`. -## Reuse BubAgent +## Reuse BubAgent {#reuse-bubagent} `BubAgent` combines the core loop with Bub settings, skills, tool policies, and plugin hooks. A host can load installed plugins with `framework.load_hooks()`, enter `framework.running()`, then construct `BubAgent(framework)`. Its `run_stream(*, session_id, prompt, state=None, ...)` returns a stream for the host to consume and close. @@ -164,7 +164,7 @@ Choose this when you also want those application defaults. The builtin system pr The direct stream call bypasses the initial `build_prompt`, `run_model` / `run_model_stream`, `save_state`, rendering, and dispatch hooks. The host handles output, saves its own state, and coordinates concurrent runs on one session. Use `process_inbound()` when you need the complete message pipeline; even with `stream_output=True`, that method consumes the stream and returns a completed `TurnResult`. -## Keep building +## Keep building {#keep-building} - [Tools and CodeMode](/docs/sdk/tools/) explains how Agent uses the tools you supply. - [Tape and context](/docs/concepts/tape/) covers storage and handoffs. diff --git a/website/src/content/docs/docs/sdk/tools.mdx b/website/src/content/docs/docs/sdk/tools.mdx index ac446401e..00f98d085 100644 --- a/website/src/content/docs/docs/sdk/tools.mdx +++ b/website/src/content/docs/docs/sdk/tools.mdx @@ -7,7 +7,7 @@ A tool gives your agent an action: look up an order, read a file, or call a serv This page covers the shared tool contract and direct SDK use. For installation and a complete model loop, start with [Build your own agent](/docs/sdk/). -## Define the action +## Define the action {#define-the-action} `Tool.from_callable()` reads parameter types from annotations and a description from the docstring. It accepts synchronous and asynchronous functions and returns a tool for you to pass to an Agent. @@ -29,7 +29,7 @@ The runtime keeps the name `math.add`; model-facing names replace dots with unde Plain `Agent` offers all supplied tools except those marked `exposure="command"`. Deferred loading, the `allowed_tools` state value, and the `state["code_mode"]` switch are [BubAgent policies](/docs/app/tools/). To choose tools per run in your own agent, override [`prepare_tools()`](/docs/sdk/options/#customize-how-a-step-is-prepared). -## Return data the caller can use +## Return data the caller can use {#return-data-the-caller-can-use} Return a dictionary, list, typed value, or string that represents the result. A `renderer` can turn structured data into text when the model calls the tool directly: @@ -57,7 +57,7 @@ order_tool = Tool.from_callable( Tool execution errors become tool results in the model loop, giving the model a chance to respond or retry. A direct command call propagates its error to the caller. [Event handlers](/docs/concepts/events/#handlers) have their own exception behavior. -## Use the current conversation +## Use the current conversation {#use-the-current-conversation} Pass `context=True` to receive a `ToolContext` as the keyword argument `context`. It carries the current tape, turn state, run ID, and event bus. @@ -76,7 +76,7 @@ remember_tool = Tool.from_callable(remember, context=True) This writes a business event to the tape. The default context selector reads conversation messages and tool exchanges; including this event in a model request requires your own selection logic. Use [Tape and context](/docs/concepts/tape/#context) to decide which saved information the model should read. -## Compose tools with CodeMode +## Compose tools with CodeMode {#compose-tools-with-codemode} CodeMode lets the model write Python that calls your tools. It provides a `run_code` tool and a prompt section describing the tools available inside that code. Add both explicitly to a plain Agent: @@ -117,7 +117,7 @@ The code receives a Republic bridge for chat, embedding, and decision model call `run_code` returns printed output and attached media. Its default timeout is 120 seconds; callers may pass `timeout_seconds`. With `LocalEnvironment`, each call starts a Python process with the host user's permissions and stops that process and its descendants on completion or cancellation. Choose another environment when you need a different execution boundary. -## Choose an execution environment +## Choose an execution environment {#choose-an-execution-environment} The `Environment` interface in `bub.environment` supplies process execution, file access, and Python execution. `CodeMode(environment=...)` uses the environment you provide. In the Bub application, the [`provide_environment` hook](/docs/reference/hooks/) selects an environment per session. @@ -131,7 +131,7 @@ The `Environment` interface in `bub.environment` supplies process execution, fil The environment runs the generated code and calls back to the host to execute your Python tools. Close custom environments when your application stops using them. -## Call a tool without a model +## Call a tool without a model {#call-a-tool-without-a-model} Set `command_prefix` to accept text commands. This example uses the `add_tool` defined above and needs no model credentials: diff --git a/website/src/content/docs/zh-cn/docs/app/hooks.mdx b/website/src/content/docs/zh-cn/docs/app/hooks.mdx index 6e91755ef..6b6380deb 100644 --- a/website/src/content/docs/zh-cn/docs/app/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/app/hooks.mdx @@ -9,7 +9,7 @@ description: 通过 hooks 调整提示词、控制调用、保存应用状态并 [Hooks 参考](/zh-cn/docs/reference/hooks/)定义了签名、优先级和生命周期规则。 -## 调整提示词 +## 调整提示词 {#change-the-prompt} 用 `build_prompt` 替换收到的内容: @@ -36,7 +36,7 @@ def system_prompt(prompt, state): return "Explain which files support your answer." ``` -## 控制一次模型或工具调用 +## 控制一次模型或工具调用 {#control-a-model-or-tool-call} 保留内置 agent,并调整某次调用时,使用 agent 拦截 hooks。例如,限制一次请求的输出 token 数: @@ -68,9 +68,9 @@ def before_tool_call(call: ToolCall, state): 由你的授权逻辑设置 `can_cancel_orders`,hook 根据这个值允许或拒绝调用。 -这些拦截 hooks 抛出的异常会被记录并跳过。要控制执行,使用 `deny`、`replace` 或 `LLMCallDecision.finish`。[拦截契约](/zh-cn/docs/reference/hooks/#通过-pluginagenthooks-拦截调用)说明了 decision 何时终止调用链。 +这些拦截 hooks 抛出的异常会被记录并跳过。要控制执行,使用 `deny`、`replace` 或 `LLMCallDecision.finish`。[拦截契约](/zh-cn/docs/reference/hooks/#interception-through-pluginagenthooks)说明了 decision 何时终止调用链。 -## 观察已经完成的调用 +## 观察已经完成的调用 {#observe-completed-calls} 统计调用情况时,使用完成 hooks: @@ -92,7 +92,7 @@ def after_tool_call(call, result, state): 如果要替换整个模型阶段,实现 `run_model` 或 `run_model_stream` 中的一个。[第一个插件](/zh-cn/docs/app/plugins/)演示了文本替换。替换实现负责自己的执行和记录行为;只有使用内置 agent 时,才会运行它的拦截 hooks。 -## 跨 turn 保留应用状态 +## 跨 turn 保留应用状态 {#keep-application-state-across-turns} Framework 为每轮入站消息构建新的状态字典。使用 `load_state` 和 `save_state`,保留应用自己的值: @@ -120,7 +120,7 @@ turn_counter = TurnCounter() `save_state` 位于模型阶段的 `finally` 块中,前提是会话解析、状态加载和提示词准备已经成功。这些准备阶段发生错误时,会跳过保存 hook。会话历史另有 [Tape 生命周期](/zh-cn/docs/concepts/tape/#state)。 -## 提供会话存储 +## 提供会话存储 {#provide-a-tape-store} 返回希望应用使用的 store: @@ -158,7 +158,7 @@ def provide_tape_sidecar(): [公共 Tape 说明](/zh-cn/docs/concepts/tape/)介绍了 sidecar 的命名和合并行为。 -## 添加应用命令 +## 添加应用命令 {#add-an-application-command} `register_cli_commands` 接收根 Typer app: diff --git a/website/src/content/docs/zh-cn/docs/app/plugins.mdx b/website/src/content/docs/zh-cn/docs/app/plugins.mdx index 2472b08eb..d5ffdd08a 100644 --- a/website/src/content/docs/zh-cn/docs/app/plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/app/plugins.mdx @@ -88,7 +88,7 @@ uv run bub run "hello from my plugin" 如果没有看到 `echo`,检查安装插件的环境是否就是运行 `bub` 的环境、入口点组名,以及启动日志中的导入错误。修改包元数据后,需要重新安装包。如果已经列出却返回其他回复,检查其他模型 hooks 及其优先级。 -Bub 先注册内置 hooks,再注册入口点插件。同等优先级通常按注册顺序的逆序调用;`tryfirst` 和 `trylast` 会影响顺序。[调用规则](/zh-cn/docs/reference/hooks/#优先级与分发)说明了哪个实现先运行。 +Bub 先注册内置 hooks,再注册入口点插件。同等优先级通常按注册顺序的逆序调用;`tryfirst` 和 `trylast` 会影响顺序。[调用规则](/zh-cn/docs/reference/hooks/#priority-and-dispatch)说明了哪个实现先运行。 ## 选择入口点形式 diff --git a/website/src/content/docs/zh-cn/docs/app/tools.mdx b/website/src/content/docs/zh-cn/docs/app/tools.mdx index e4712b6f2..45d41fd15 100644 --- a/website/src/content/docs/zh-cn/docs/app/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/app/tools.mdx @@ -42,7 +42,7 @@ class OrdersPlugin: 调用 `BubAgent.run_stream` 时,传入 `allowed_tools` 限制该 turn 里模型可用的工具。它接受运行时名称或模型名称。`None` 允许全部实例工具,空集合不允许任何工具。未知名称会抛出 `ValueError`。 -这个筛选适用于模型和 code mode 中的工具。逗号命令直接使用 agent 实例的工具。如果规则也需要约束命令执行,使用[工具调用 decision](/zh-cn/docs/app/hooks/#控制一次模型或工具调用)。 +这个筛选适用于模型和 code mode 中的工具。逗号命令直接使用 agent 实例的工具。如果规则也需要约束命令执行,使用[工具调用 decision](/zh-cn/docs/app/hooks/#control-a-model-or-tool-call)。 ## 按需加载工具 @@ -83,15 +83,15 @@ turn 的允许列表同时约束工具的展示和加载。code mode 中,允 如果排除了 `run_code`,该 turn 按关闭 code mode 时的规则选择工具。应用把 Python 工具声明文件写入 `/codemode/`,并把路径加入系统提示词。`await tools.orders_get(order_id="123")` 这样的 Python 调用使用已选工具,并经过应用的拦截 hooks。 -应用把 CodeMode 接到会话的执行环境和模型设置上。代码可以使用 [CodeMode](/zh-cn/docs/sdk/tools/#组合工具与-codemode) 中介绍的 Republic bridge 和媒体附加工具。通过 bridge 使用模型时,凭据保留在宿主侧;默认的本地执行环境仍在这个宿主上运行代码。 +应用把 CodeMode 接到会话的执行环境和模型设置上。代码可以使用 [CodeMode](/zh-cn/docs/sdk/tools/#compose-tools-with-codemode) 中介绍的 Republic bridge 和媒体附加工具。通过 bridge 使用模型时,凭据保留在宿主侧;默认的本地执行环境仍在这个宿主上运行代码。 -`BubAgent` 读取 `state["code_mode"]` 和 `deferred` 标记,执行这些筛选规则。使用普通 `core.Agent` 时,需要[显式组合 CodeMode](/zh-cn/docs/sdk/tools/#组合工具与-codemode);如需延迟加载,也由你提供相应策略。 +`BubAgent` 读取 `state["code_mode"]` 和 `deferred` 标记,执行这些筛选规则。使用普通 `core.Agent` 时,需要[显式组合 CodeMode](/zh-cn/docs/sdk/tools/#compose-tools-with-codemode);如需延迟加载,也由你提供相应策略。 ## 选择执行环境 内置 `bash`、`bash.output`、`bash.kill`、`fs.*` 和 `run_code` 使用会话的 `Environment`。默认是运行在宿主上的 `LocalEnvironment`。 -插件通过 `provide_environment(session_id, workspace)` hook 提供其他环境。Framework 为每个会话缓存一个环境,并在退出 `framework.running()` 时关闭。[工具与 CodeMode](/zh-cn/docs/sdk/tools/#选择执行环境)说明环境接口。 +插件通过 `provide_environment(session_id, workspace)` hook 提供其他环境。Framework 为每个会话缓存一个环境,并在退出 `framework.running()` 时关闭。[工具与 CodeMode](/zh-cn/docs/sdk/tools/#choose-an-execution-environment)说明环境接口。 自定义应用工具可以用 `bub.builtin.environment` 中的 `environment_from_state(context.state)` 取得环境。Python 工具本身在宿主侧运行,需要时再显式调用环境来完成相应操作。 @@ -99,4 +99,4 @@ turn 的允许列表同时约束工具的展示和加载。code mode 中,允 把工作区通用指令写入 `AGENTS.md`,用 [技能](/zh-cn/docs/app/skills/)提供按任务加载的流程。技能可以告诉模型何时调用工具,Python 函数则定义工具执行的动作。 -逗号命令让使用者无需请求模型,就能调用已注册的工具。语法见[文本命令](/zh-cn/docs/sdk/tools/#不经过模型调用工具)。 +逗号命令让使用者无需请求模型,就能调用已注册的工具。语法见[文本命令](/zh-cn/docs/sdk/tools/#call-a-tool-without-a-model)。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx index 454f5d2ba..98f44625c 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx @@ -29,7 +29,7 @@ Agent 的本质是一个循环:模型读取上下文并决定调用哪个工 - 如果你想**开箱即用 Bub**,那么请直接使用 Bub 应用层的 [App](/zh-cn/docs/app/workspace/)。 ## 核心设计理念 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx index 0aa19c065..fb2407b21 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx @@ -5,7 +5,7 @@ description: 跟着文档一步步安装 Bub,提供了多种安装途径。 import { Tabs, TabItem, Aside } from '@astrojs/starlight/components'; -## 安装 CLI 应用 +## 安装 CLI 应用 {#app-install} @@ -40,7 +40,7 @@ uv tool update-shell 如果当前环境提示找不到命令,请检查环境变量是否包含了可执行文件所在路径,或者手动把 `~/.local/bin` 加入 PATH。 -## 安装 SDK +## 安装 SDK {#agent-install} 自 0.6.0 起,Bub 默认只包含核心,可以根据使用对应包管理工具将 Bub 作为依赖安装到项目中。这里以 uv 为例: @@ -49,7 +49,7 @@ uv add bub uv run python -c "from bub.core import Agent; print(Agent.__name__)" ``` -## 从源码运行 +## 从源码运行 {#from-source} 克隆项目仓库,按如下步骤同步依赖即可: diff --git a/website/src/content/docs/zh-cn/docs/integrations/observability.mdx b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx index e298a42c3..7866567d7 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/observability.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx @@ -14,7 +14,7 @@ description: 检查会话记录,并把模型与工具调用轨迹导出到 Pho - Bub 已安装,且 `bub --help` 可以运行。 - 一个工作区,其中 `bub run "What tools do you have?"` 能调用已配置的模型。 - 如果要本地运行 Phoenix,需要 Docker 或 Podman。 -- 如果要添加追踪依赖,请先激活[应用虚拟环境](/zh-cn/docs/getting-started/install/#app-development)。 +- 如果要添加追踪依赖,请先激活[应用虚拟环境](/zh-cn/docs/getting-started/install/#app-install)。 ## 1. 询问 Bub 当前 tape diff --git a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx index 98c560a3f..727529de6 100644 --- a/website/src/content/docs/zh-cn/docs/reference/hooks.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/hooks.mdx @@ -7,7 +7,7 @@ Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 下文的“首个”指按 hook 优先级取得第一个非 `None` 的结果,“全部”指调用每个实现。普通 hook 抛出异常时,本次分发停止,异常向上传播;[agent 拦截 hooks](#通过-pluginagenthooks-拦截调用) 和 `on_error` 有各自的异常处理规则。 -## Turn Hooks +## Turn Hooks {#turn-hooks} | Hook | 签名 | 行为 | | --- | --- | --- | @@ -25,7 +25,7 @@ Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 模型适配器先按插件注册顺序的逆序选择文本或流式 hook,再按 hook 优先级分发选中的名称。它可以把流收集为文本,也可以把文本回复包装成流。每个插件选择一种模型 hook 实现。 -## 应用资源与配置 +## 应用资源与配置 {#application-resources-and-configuration} | Hook | 签名 | 行为 | | --- | --- | --- | @@ -46,7 +46,7 @@ Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 同步分发跳过 awaitable 返回值,并记录 `hook.async_not_supported` 告警。资源 provider 可以返回同步或异步生成器,Bub 会把它们作为上下文管理器进入。 -## Agent 拦截 Hooks +## Agent 拦截 Hooks {#agent-interception-hooks} | Hook | 签名 | 行为 | | --- | --- | --- | @@ -57,7 +57,7 @@ Hooks 把插件接到 Bub 应用上。本页列出 `BubHookSpecs` 声明的 26 -## 优先级与分发 +## 优先级与分发 {#priority-and-dispatch} Bub 先注册内置 hooks,再注册已安装插件的入口点。普通实现优先级相同时,后注册的先运行。`@hookimpl(tryfirst=True)` 和 `@hookimpl(trylast=True)` 会改变优先级。这些标记与注册顺序共同决定哪个实现先运行。 @@ -67,13 +67,13 @@ Bub 先注册内置 hooks,再注册已安装插件的入口点。普通实现 `bub hooks` 按 pluggy 内部保存的顺序显示绑定,用于确认注册结果。执行时遵循上面的优先级规则。 -## 提示词内容 +## 提示词内容 {#prompt-content} `build_prompt` 可以返回包含字符串和 Republic 媒体的列表,媒体类型包括 `republic.Image`、`Audio` 和 `Video`。使用 `republic.image(...)`、`republic.audio(...)`、`republic.video(...)` 构造媒体。 旧格式的字符串和内容块字典仍可使用。接收提示词的 hooks(`run_model`、`run_model_stream`、`continue_prompt`、`system_prompt`)收到的是旧格式:没有媒体时是字符串,否则是带媒体 URL 的内容块。内联媒体使用 data URL。`bub.prompt.to_content(prompt)` 可以转换为用户内容。 -## 运行时资源 +## 运行时资源 {#runtime-resources} `framework.running()` 先进入 lifespan providers,再获取会话存储和 steering inbox。退出时按相反顺序关闭已经进入的资源,发生错误时也是如此。执行环境缓存也会在作用域退出时关闭。 @@ -85,14 +85,14 @@ Bub 先注册内置 hooks,再注册已安装插件的入口点。普通实现 Tape sidecar providers 提供具名的关联 tape。Bub 把它们挂载到应用的根 Tape 上,fork、merge、archive 和 reset 都会处理已挂载的 sidecars。移除 provider 后,已保存的数据仍然保留。公共存储语义见 [Tape 与上下文](/zh-cn/docs/concepts/tape/)。 -## 通过 PluginAgentHooks 拦截调用 +## 通过 PluginAgentHooks 拦截调用 {#interception-through-pluginagenthooks} `AgentPlugins.attach()` 把 `PluginAgentHooks` 订阅到核心 agent 的 LLM 和工具开始、完成事件上;`BubAgent` 将它接到自身。应用插件通过这个适配器介入调用。 Before hooks 按 hook 优先级运行,每个实现都能看到前序实现的更新。`LLMCallDecision.finish(text)`、`ToolCallDecision.replace(result)` 和 `ToolCallDecision.deny(message)` 会停止后续 before 实现。LLM finish decision 不调用供应商,也没有对应的 `after_llm_call`。 -适配器捕获每个拦截 hook 的普通异常,记录 `hook.agent_hook_failed` 后继续。需要拒绝调用时,返回 decision。直接注册到[核心 EventBus](/zh-cn/docs/concepts/events/#处理器) 的 handler 会向上传播异常。 +适配器捕获每个拦截 hook 的普通异常,记录 `hook.agent_hook_failed` 后继续。需要拒绝调用时,返回 decision。直接注册到[核心 EventBus](/zh-cn/docs/concepts/events/#handlers) 的 handler 会向上传播异常。 After hooks 观察成功调用和普通异常失败,取消与消费方关闭不会触发。`after_tool_call` 也会处理拒绝或替换的调用,各个实现共享同一个结果对象。修改 `result.result` 会改变向后传递的值,`result.error` 则保留原始失败信息。 diff --git a/website/src/content/docs/zh-cn/docs/reference/python-api.mdx b/website/src/content/docs/zh-cn/docs/reference/python-api.mdx index 6119de12b..0163a2307 100644 --- a/website/src/content/docs/zh-cn/docs/reference/python-api.mdx +++ b/website/src/content/docs/zh-cn/docs/reference/python-api.mdx @@ -48,7 +48,7 @@ from bub.hooks.interception import ( ) ``` -`BubError.kind` 表示错误类别,`as_dict()` 返回适合传输的载荷。拦截载荷与 `bub.core.events` 共用类型。插件如何使用它们,以及异常如何处理,见 [Hooks](/zh-cn/docs/reference/hooks/#通过-pluginagenthooks-拦截调用)。 +`BubError.kind` 表示错误类别,`as_dict()` 返回适合传输的载荷。拦截载荷与 `bub.core.events` 共用类型。插件如何使用它们,以及异常如何处理,见 [Hooks](/zh-cn/docs/reference/hooks/#interception-through-pluginagenthooks)。 ## 应用 diff --git a/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx b/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx index 437400cf2..660fbeda6 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/reuse-plugins.mdx @@ -9,7 +9,7 @@ description: 将插件提供的工具和 hooks 接入自己的 Agent,保留现 使用 `core.Agent` 时,由你选择组件并接入 hooks。下面分别取用搜索插件的工具、适配上下文插件的调用 hooks,每个示例都说明了该插件需要的依赖。后台服务还需要各自的启动和关闭流程。 -## 加入搜索工具 +## 加入搜索工具 {#add-a-search-tool} [`bub-web-search`](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-web-search) 提供了可供 `core.Agent` 使用的搜索工具。将它安装到运行服务的 Python 环境中。本文示例使用 `bub-contrib` 的 `dev` 分支插件: @@ -68,11 +68,11 @@ if __name__ == "__main__": `register_tools()` 还会更新进程内的全局工具注册表,请在服务启动时完成这些准备。Agent 使用显式传入或加入的工具;本例取用返回的搜索工具,包注册的其他工具(如 Jina 的 `web.read`)可按需另行加入。 -## 扩展运行 +## 扩展运行 {#extend-the-run} `AgentPlugins` 把插件的 hooks 接到你的 Agent 事件上。 -### 模型与工具调用 +### 模型与工具调用 {#model-and-tool-calls} [`bub-context-budget`](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-context-budget) 在模型调用前后工作。当估算的上下文超过阈值时,它会加入一条指令,请模型调用 `tape.handoff`。将这个工具交给 Agent,再把插件 hooks 接到 Agent 事件上即可。 @@ -109,7 +109,7 @@ Handoff 把摘要保存在锚点上。下一次模型请求需要这份摘要时 适配器会捕获普通插件异常并跳过失败的 hook。需要阻止工具执行时,hook 应返回 `ToolCallDecision.deny(...)`。具体拦截语义见 [Agent 事件](/zh-cn/docs/concepts/events/)。 -### 其他 hooks +### 其他 hooks {#other-hooks} 其余 hooks 决定一次运行中有什么。每个 hook 对应核心 Agent 的一个扩展点: @@ -139,7 +139,7 @@ async with plugins.running(): `build_prompt`、`run_model`、`save_state`、`render_outbound`、`dispatch_outbound` 等消息流程 hooks 不属于 agent 循环,只在 `process_inbound()` 中运行。`plugins=[...]` 只注册列出的插件。多个插件实现同一个 hook 时都会执行:重名的工具、存储和 Tape 上下文取优先级最高者,提示词片段按优先级从低到高拼接。不带参数的 `AgentPlugins()` 和 `bub` 命令一样,加载 Bub 内置 hooks 和已安装的插件。传入 `framework` 则使用已有 framework 的 hooks,同时传入的 `plugins` 会注册到它上面。 -## 选择接入方式 +## 选择接入方式 {#choose-how-to-connect} 宿主需要提供什么,取决于插件实现了哪些能力: @@ -154,7 +154,7 @@ async with plugins.running(): 接入 MCP 时,按照插件的[嵌入示例](https://github.com/bubbuild/bub-contrib/tree/dev/packages/bub-mcp#embedding),用 `MCPChannel.connect()` 建立连接,通过 `bind_agent()` 把工具绑定到 `BubAgent`,最后调用 `stop()` 关闭连接。宿主在使用 `framework.running()` 之外,还需要管理这段生命周期。 -## 复用 BubAgent +## 复用 BubAgent {#reuse-bubagent} `BubAgent` 在内核循环上接入 Bub 设置、技能、工具策略和插件 hooks。宿主可以通过 `framework.load_hooks()` 加载已安装插件,进入 `framework.running()` 后构造 `BubAgent(framework)`。它的 `run_stream(*, session_id, prompt, state=None, ...)` 返回由宿主消费和关闭的流。 @@ -164,7 +164,7 @@ async with plugins.running(): 直接调用流入口会跳过初始 `build_prompt`、`run_model` / `run_model_stream`、`save_state`、渲染和分发 hooks。宿主负责输出、自有状态的保存,以及同一会话的并发协调。需要完整消息流程时,使用 `process_inbound()`;即使设置 `stream_output=True`,它也会自行消费流,再返回完整的 `TurnResult`。 -## 继续构建 +## 继续构建 {#keep-building} - [工具与 CodeMode](/zh-cn/docs/sdk/tools/):Agent 如何使用你提供的工具。 - [Tape 与上下文](/zh-cn/docs/concepts/tape/):存储与 handoff。 diff --git a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx index bb593d08f..310089d02 100644 --- a/website/src/content/docs/zh-cn/docs/sdk/tools.mdx +++ b/website/src/content/docs/zh-cn/docs/sdk/tools.mdx @@ -7,7 +7,7 @@ description: 定义一个动作,再直接交给 Agent,或通过 CodeMode 从 本页说明共同的工具接口和 SDK 接入方式。安装步骤与完整的模型循环示例见[构建自己的 Agent](/zh-cn/docs/sdk/)。 -## 定义工具 +## 定义工具 {#define-the-action} `Tool.from_callable()` 从类型注解生成参数定义,从 docstring 读取描述。它支持同步和异步函数,返回的工具由你传给 Agent。 @@ -29,7 +29,7 @@ agent = Agent(tools=[add_tool]) 普通 `Agent` 会把传入的工具提供给模型,仅排除 `exposure="command"`。延迟加载、`allowed_tools` 状态和 `state["code_mode"]` 开关属于 [BubAgent 的应用策略](/zh-cn/docs/app/tools/)。在自己的 Agent 中按轮次选择工具,可以覆写 [`prepare_tools()`](/zh-cn/docs/sdk/options/#定制步骤准备方式)。 -## 返回可用数据 +## 返回可用数据 {#return-data-the-caller-can-use} 工具可以返回字典、列表、带类型的值或字符串。模型直接调用工具时,可以用 `renderer` 将结构化结果转成文本: @@ -55,9 +55,9 @@ order_tool = Tool.from_callable( `Tool.render(result)` 使用这个 renderer。默认情况下,字符串直接保留,Pydantic 模型与可序列化的值转为 JSON,其他值使用 `str()`。面向模型的执行器在 `ToolCallFinished` 之前渲染结果,因此处理器看到的是将交给模型的内容。CodeMode 使用的执行器会保留结构化结果。 -在模型循环中,工具执行错误会作为工具结果交回模型,让模型决定如何回答或重试。直接通过命令调用时,错误会传播给调用方。[事件处理器](/zh-cn/docs/concepts/events/#处理器)有自己的异常规则。 +在模型循环中,工具执行错误会作为工具结果交回模型,让模型决定如何回答或重试。直接通过命令调用时,错误会传播给调用方。[事件处理器](/zh-cn/docs/concepts/events/#handlers)有自己的异常规则。 -## 访问当前会话 +## 访问当前会话 {#use-the-current-conversation} 传入 `context=True`,工具就会通过关键字参数 `context` 接收 `ToolContext`。其中包含当前 Tape、turn 状态、run ID 和事件总线。 @@ -76,7 +76,7 @@ remember_tool = Tool.from_callable(remember, context=True) 这会在 Tape 中写入一条业务事件。默认上下文选择器读取会话消息与工具交互;要把这条事件交给模型,需要提供自己的选择逻辑。哪些已保存的信息应交给模型,由[Tape](/zh-cn/docs/concepts/tape/#context)中的选择规则决定。 -## 组合工具与 CodeMode +## 组合工具与 CodeMode {#compose-tools-with-codemode} CodeMode 让模型编写 Python 来调用工具。它提供 `run_code` 工具,以及描述代码中可用工具的提示词片段。使用普通 Agent 时,需要显式加入这两部分: @@ -117,7 +117,7 @@ print(order["status"]) `run_code` 返回打印的输出和附带媒体,默认超时为 120 秒,可以通过 `timeout_seconds` 调整。使用 `LocalEnvironment` 时,每次调用都会启动一个拥有宿主用户权限的 Python 进程,在完成或取消时停止它及其子进程。如果需要不同的执行边界,请选择其他环境。 -## 选择执行环境 +## 选择执行环境 {#choose-an-execution-environment} `bub.environment` 中的 `Environment` 接口提供进程执行、文件访问和 Python 执行能力。`CodeMode(environment=...)` 使用你传入的环境。在 Bub 应用中,[`provide_environment` hook](/zh-cn/docs/reference/hooks/)负责为会话选择环境。 @@ -131,7 +131,7 @@ print(order["status"]) 环境执行生成的代码,通过回调交给宿主执行你的 Python 工具。应用不再使用自定义环境时,需要关闭它。 -## 不经过模型调用工具 +## 不经过模型调用工具 {#call-a-tool-without-a-model} 设置 `command_prefix` 后,就可以接收文本命令。下面使用前面定义的 `add_tool`,不需要模型凭据: diff --git a/website/src/integrations/heading-anchors.mjs b/website/src/integrations/heading-anchors.mjs new file mode 100644 index 000000000..1aa3fb67d --- /dev/null +++ b/website/src/integrations/heading-anchors.mjs @@ -0,0 +1,32 @@ +// Starlight derives a heading's id from its text, so a section keeps a different +// anchor in every locale (`## Storage` → `#storage`, `## 存储` → `#存储`). Any +// cross-page link therefore breaks as soon as one locale renames a heading. +// +// Enable Sätteri's heading attributes so a heading can carry an explicit id +// (`## 存储 {#storage}`), which every locale then shares. + +import { isSatteriProcessor, satteri } from '@astrojs/markdown-satteri'; + +/** @returns {import('astro').AstroIntegration} */ +export default function headingAnchors() { + return { + name: 'bub-heading-anchors', + hooks: { + 'astro:config:setup'({ config, updateConfig }) { + const processor = config.markdown.processor; + if (!isSatteriProcessor(processor)) { + throw new Error('Explicit heading anchors require the Sätteri Markdown processor.'); + } + const options = processor.options; + updateConfig({ + markdown: { + processor: satteri({ + ...options, + features: { ...options.features, headingAttributes: true }, + }), + }, + }); + }, + }, + }; +} From d9eec8d641452a070065a6b649e8c1cfb6487431 Mon Sep 17 00:00:00 2001 From: 100gle Date: Thu, 22 Oct 2026 16:28:56 +0800 Subject: [PATCH 17/28] docs(concepts): expand the concepts into the why behind the design The concepts section introduced four ideas but never showed them working. The overview repeated the layers and principles from the introduction as prose instead of explaining how a message moves through the app, and the four topic pages read as flat prose piles with no diagrams and no emphasis. Rewrite each page around one question: - Overview: walk one message through a turn and the agent loop, then keep the layers, terms, and trade-offs that the other pages depend on. - Turn: explain why the stages exist, what crosses between them, and move the failure paths into an aside. - Tape: split the page along history versus context, which is the distinction everything else follows from, and add the handoff and fork diagrams. - Events: separate AgentEvent, StreamEvent, and TapeEntry up front, then derive observing, intercepting, and recording from that split. - Extension points: turn the link table into a decision page with questions to ask first and a rule for keeping one owner per job. Diagrams use the Mermaid pipeline the site already builds with Pele. --- website/TRANSLATING.md | 2 + .../src/content/docs/docs/concepts/events.mdx | 159 ++++++++++++----- .../docs/docs/concepts/extension-points.mdx | 95 ++++++++-- .../src/content/docs/docs/concepts/index.mdx | 96 +++++++--- .../src/content/docs/docs/concepts/tape.mdx | 168 +++++++++++++----- .../src/content/docs/docs/concepts/turn.mdx | 87 ++++++--- .../docs/docs/getting-started/index.mdx | 2 +- .../docs/zh-cn/docs/concepts/events.mdx | 127 ++++++++++--- .../zh-cn/docs/concepts/extension-points.mdx | 93 ++++++++-- .../docs/zh-cn/docs/concepts/index.mdx | 100 ++++++++--- .../content/docs/zh-cn/docs/concepts/tape.mdx | 144 +++++++++++---- .../content/docs/zh-cn/docs/concepts/turn.mdx | 87 ++++++--- .../docs/zh-cn/docs/getting-started/index.mdx | 2 +- website/src/integrations/pele.mjs | 7 + website/src/scripts/pele.ts | 4 + website/wrangler.jsonc | 2 +- 16 files changed, 899 insertions(+), 276 deletions(-) diff --git a/website/TRANSLATING.md b/website/TRANSLATING.md index d386de5bf..785277475 100644 --- a/website/TRANSLATING.md +++ b/website/TRANSLATING.md @@ -20,6 +20,8 @@ Anything not listed is an ordinary technical word. Translate it the usual way; j | `envelope` | `envelope` | Never 信封. | | `trace` | `trace` | Never 轨迹, 追踪. | | `CodeMode` | `CodeMode` | The class. The feature is `code mode`. Never 代码模式. | +| `harness` | `Harness` | The app layer around the loop. Keep the capital H. | + ## Punctuation (zh-cn) diff --git a/website/src/content/docs/docs/concepts/events.mdx b/website/src/content/docs/docs/concepts/events.mdx index 86683fc78..39fa8b071 100644 --- a/website/src/content/docs/docs/concepts/events.mdx +++ b/website/src/content/docs/docs/concepts/events.mdx @@ -1,23 +1,46 @@ --- title: Events -description: Watch a run, change or stop a model or tool call, and record the events that you need. +description: Watch a run, change or stop a model or tool call, and record the events you need. --- -Agent events let you watch a run. They also let you decide before a model or tool call. Every Agent creates an `EventBus` and subscribes a `TapeRecorder`. You add handlers through `agent.events`. +import { Aside } from '@astrojs/starlight/components'; -Three kinds of information have three different jobs: +Agent events let you see a run and let you decide before a model or tool call happens. Every agent creates an `EventBus` and registers a `TapeRecorder`. You add handlers through `agent.events`. -| Type | Job | How to read it | -| --- | --- | --- | -| `AgentEvent` | Lifecycle of the run and interception of calls. | `agent.events.on(...)` | -| `StreamEvent` | Output for the caller, such as text and tool progress. | Iterate over the stream that `agent.run()` returns. | -| `TapeEntry` | The saved conversation and selected runtime records. | Query the [tape](/docs/concepts/tape/). | +Events are the only cross-cutting seam in Bub. Logging, permissions, budgets, auditing, and UI progress can all be written as handlers alone, without touching the loop or the tools. + +## Three kinds of information {#three-kinds} + +"Event" means three different things in Bub, and mixing them up is the easiest way to write a bug. Separate them first: + +| Type | Purpose | How to read it | Who reads it | +| --- | --- | --- | --- | +| `AgentEvent` | Run lifecycle, and call interception. | `agent.events.on(...)` | host code and plugins | +| `StreamEvent` | Output for the caller, such as text and tool progress. | iterate the stream from `agent.run()` | code that calls the agent | +| `TapeEntry` | The stored conversation and some run records. | query the [tape](/docs/concepts/tape/) | the model, on later runs | + +How the three relate: + +```mermaid +flowchart LR + loop["agent loop"] + loop -->|"emit"| bus["EventBus"] + bus --> rec["TapeRecorder
(builtin, registered first)"] + bus --> yours["your handlers"] + loop -->|"yield"| stream["StreamEvent stream"] + rec --> tape[("tape entries")] + stream --> caller["the caller"] + yours -->|"set decision / write tape"| loop + tape -->|"next run reads context"| loop +``` -The agent loop controls execution. It calls the event handlers at fixed points, before it continues. TapeRecorder saves selected exchanges and lifecycle records. Your handlers can record the other events that you need to keep. +Mind the direction of the arrows. `StreamEvent` is a **one-way exit**: you can consume it, but you cannot change the run through it. To change the run, use an `AgentEvent` handler. Conversely, a `TapeEntry` is **after the fact**; writing one does not affect the step in progress. -## Handlers +The loop controls execution, calls event handlers at fixed points, and only then moves on. TapeRecorder saves selected round-trips and lifecycle records. For anything else you want kept, write your own handler. -This example uses the `agent` from the [SDK guide](/docs/sdk/): +## Handlers {#handlers} + +The examples below use the `agent` from the [SDK guide](/docs/sdk/): ```python from bub.core.events import StepFinished, ToolCallFinished @@ -35,15 +58,35 @@ def report_step(event: StepFinished) -> None: agent.events.on(StepFinished, report_step) ``` -To remove a handler, call `agent.events.off(StepFinished, report_step)`. A handler for a base class also receives its subclasses. A handler for `AgentEvent` receives every agent event that is delivered. +The two registration forms are equivalent: the decorator suits a new function, and passing it directly suits an existing one. To remove it, call `agent.events.off(StepFinished, report_step)`. + +A handler registered on a base class also receives subclass events. Every agent event derives from `AgentEvent`, so a handler registered for `AgentEvent` receives all of them — useful for uniform logging or timing, but watch the volume. + +### Delivery rules {#delivery} + +Four rules decide what a handler may assume about ordering: -For each emission, the matching handlers run in the order of registration. The bus waits for each async handler before it starts the next one. The bus ignores the return value of a handler. An exception goes to the caller of `emit()` and can fail the run. If work must continue after an observer fails, catch the exception inside that observer. +1. **Registration order.** Within one dispatch, matching handlers run in the order they were registered. +2. **Async handlers finish first.** The bus awaits one async handler before calling the next. +3. **Return values are ignored.** Whatever a handler returns, the bus discards. To influence the run, mutate fields on the event. +4. **Exceptions propagate.** A handler exception is raised out of `emit()`, and can fail the whole run. + +Rule 4 catches people. A handler that only observes should catch its own errors: + +```python +@agent.events.on(ToolCallFinished) +async def audit_tool(event: ToolCallFinished) -> None: + try: + await send_to_audit_log(event.call.tool, event.result) + except Exception: + logger.warning("audit failed tool={}", event.call.tool) +``` -This order applies to one emission. Concurrent tool calls or runs can emit events at the same time. Shared handler state therefore needs its own coordination. +Another edge: **ordering applies to one dispatch only.** Concurrent tool calls or two runs may emit events at the same time. State shared between handlers is still yours to coordinate. -## Call control +## Call control {#call-control} -To stop a call, assign a decision to the `decision` field of the event: +To stop a call, assign the event's `decision` field. This is the only place in the event system where you can rewrite the loop's behavior. ```python from bub.core.events import ToolCallDecision, ToolCallStarted @@ -55,39 +98,59 @@ def require_refund_permission(event: ToolCallStarted) -> None: event.decision = ToolCallDecision.deny("Refunds require approval.") ``` -`deny()` skips the tool and produces a tool error. `replace(result)` skips the tool and supplies a result. Both stop delivery to the later handlers of that `ToolCallStarted` event. The executor still emits `ToolCallFinished` for the outcome. A model loop receives the error as a tool result. A direct command gives the error to its caller. +Three verdicts, three different effects: + +| Verdict | Did the tool run? | What the model sees | Afterwards | +| --- | --- | --- | --- | +| `deny(message)` | no | a tool error | this dispatch stops | +| `replace(result)` | no | the result you supply | this dispatch stops | +| change `event.call` | yes, with new arguments | the real result | dispatch continues | + +`deny()` skips the tool and produces a tool error. `replace(result)` skips the tool and supplies the result directly. Both stop this `ToolCallStarted` from propagating. The executor still emits `ToolCallFinished` for the outcome. In the model loop the error goes back to the model as a tool result; in a direct command call the error is raised to the caller. + +To change arguments and continue, replace `event.call` with `dataclasses.replace(event.call, arguments=...)`. Note that this is a replacement, not a mutation: the event objects are frozen. + +To end before the model call, set `event.decision = LLMCallDecision.finish(text)` on `LLMCallStarted`. That stops later handlers and skips the provider call entirely. A skipped provider call emits no `LLMCallFinished` — **your timing and billing code must not assume the two events come in pairs**. + +### Event reference {#event-reference} -To change the arguments and continue, replace `event.call` with `dataclasses.replace(event.call, arguments=...)`. To finish before a model call, set `event.decision = LLMCallDecision.finish(text)` on `LLMCallStarted`. This stops the later handlers and skips the provider call. A skipped provider call does not emit `LLMCallFinished`. +These are all the agent events. Signatures and full fields are in the [Python API reference](/docs/reference/python-api/). -| Event | When it occurs | What you can do | +| Event | When | What you can do | | --- | --- | --- | -| `RunStarted` | A model run begins. | Read the prompt, the model, and the metadata. | -| `RunFinished` | The stream of the run closes. | Read the status, the step count, the elapsed time, and the error. | -| `StepStarted` | A model step begins. | Read the progress. | -| `StepFinished` | A model step ends. | Read the progress. When `status == "continue"`, set `next_prompt` to give the next step a user message. | -| `MessagesPending` | A step builds its model request, or ends without tool calls. | Append user messages to `messages`. They come before the next prompt. Messages that you add at the end of a step continue the run. | -| `LLMCallStarted` | Before a provider call. | Replace `request`, or set `LLMCallDecision.finish(text)`. | -| `LLMCallFinished` | A provider call completes, or fails with an exception. | Read the accumulated result. This event does not report a cancellation or a close by the consumer. | +| `RunStarted` | A model run starts. | Read the prompt, model, and metadata. | +| `RunFinished` | The run's stream closes. | Read status, step count, elapsed time, and the error. | +| `StepStarted` | A model step starts. | Read progress. | +| `StepFinished` | A model step ends. | Read progress. With `status == "continue"`, set `next_prompt` to add a user message for the next step. | +| `MessagesPending` | A step builds its model request, or a step ends without tool calls. | Append user messages to `messages`, ordered before the next prompt. Messages added at the end of a step make the run continue. | +| `LLMCallStarted` | Before the provider call. | Replace `request`, or set `LLMCallDecision.finish(text)`. | +| `LLMCallFinished` | The provider call completes or raises. | Read the accumulated result. Cancellations and consumer closes are not reported. | | `ToolCallStarted` | Before a tool runs. | Replace `call`, deny the call, or replace the result. | -| `ToolCallFinished` | A tool succeeds, fails, is denied, or is replaced. | Assign `event.result.result` to change the returned result. | -| `ChatFinished` | The exchange of a step is ready to record. | Read the exchange. | -| `CommandFinished` | A direct command finishes or fails. | Read the command, the status, and the output. | +| `ToolCallFinished` | A tool succeeded, failed, was denied, or was replaced. | Assign to `event.result.result` to change the result. | +| `ChatFinished` | This step's round-trip can be recorded. | Read the round-trip. | +| `CommandFinished` | A direct command completed or failed. | Read the command, status, and output. | -Command prompts run tools directly. They emit tool events and `CommandFinished`. See [Commands](/docs/sdk/tools/#call-a-tool-without-a-model). +`MessagesPending` deserves its own note: it is the entry point for **interjecting during a run**. `StepFinished.next_prompt` sets the next step's user message, and `MessagesPending` appends messages at the last moment before the request is built. Steering is implemented on exactly these two points — something the user adds while the model is thinking does not have to wait for the next run. -All agent events carry the turn `state`. Events that derive from `TapeEvent` also carry the active tape. For tool events, the tool context is available as `event.call.context` when the call has one. +A command prompt runs tools directly, emitting tool events and `CommandFinished`. See [calling tools without the model](/docs/sdk/tools/#call-a-tool-without-a-model). -## Recording +Every agent event carries the run's `state`. Events derived from `TapeEvent` also carry the current tape. Tool events expose `event.call.context` when the call has one. -The Agent registers TapeRecorder before your handlers. It records `RunStarted`, `RunFinished`, `StepStarted`, `StepFinished`, `ChatFinished`, and `CommandFinished`. The `ChatFinished` handler writes the messages and tool exchanges that later model calls read as context. +## Recording {#recording} -Recording is part of normal operation. It writes to the configured store. That store is in memory unless you supply another one. Recording does not depend on the tracing configuration. +The agent registers TapeRecorder before your handlers. It records `RunStarted`, `RunFinished`, `StepStarted`, `StepFinished`, `ChatFinished`, and `CommandFinished`. -To save a custom event, add a handler that writes it to the tape. Use the interception events to change a request or a result. A `ChatFinished` observer runs after the default recorder has saved that exchange. +`ChatFinished` is the important one: that handler writes the messages and tool round-trips, and later model calls get their context from what it wrote. In other words, **TapeRecorder is not optional logging — it is the producer of the context**. Delete the round-trips from the tape and the next run reads nothing of what happened. -## Custom events +Recording is part of the normal flow. It writes to the store you configured, and to memory when you configure none. Recording is independent of tracing settings: you can turn tracing off and recording continues. -Define a dataclass that derives from `AgentEvent`. Derive it from `TapeEvent` when the handlers need a tape. This example records a business event: + + +## Custom events {#custom-events} + +Define a dataclass that inherits `AgentEvent`. Inherit `TapeEvent` instead when handlers need the tape. ```python from dataclasses import dataclass @@ -112,14 +175,24 @@ async def mark_reviewed() -> None: ) ``` -`emit()` returns the event after the handlers have run. A custom mutable event can carry a response back to its emitter. Override its `stopped` property if delivery must end when that response is set. Tools can emit events through `context.events`, with the same turn state. +A custom event inherits three capabilities: the same delivery rules, access to the run's state and tape, and the ability to be intercepted. + +`emit()` returns the event once the handlers have run. A custom mutable event can carry a result back to the emitter. To stop delivery as soon as the outcome is settled, override its `stopped` property — that is how the builtin `ToolCallStarted` lets `deny` and `replace` terminate early. + +Tools can emit events through `context.events`, using that same run state. + +## App plugins {#app-plugins} + +The kernel uses events; the app uses hooks. `AgentPlugins` is the bridge. + +It maps four hooks onto call events: `before_llm_call`, `after_llm_call`, `before_tool_call`, and `after_tool_call`. It maps `continue_prompt` onto `StepFinished` and the steering inbox onto `MessagesPending`. Plugins return values as each hook requires, and the adapter changes the corresponding event. -## App plugins +`BubAgent` uses this adapter, and a core Agent [can use it the same way](/docs/sdk/reuse-plugins/#other-hooks). -`AgentPlugins` connects four hooks to the call events: `before_llm_call`, `after_llm_call`, `before_tool_call`, and `after_tool_call`. It connects the `continue_prompt` hook to `StepFinished`. It connects the steering inbox to `MessagesPending`. A plugin returns the values that the hook contract requires, and the adapter updates the event. `BubAgent` uses this adapter, and a core Agent can [use it too](/docs/sdk/reuse-plugins/#other-hooks). +The adapter follows plugin hook ordering. When a single interception hook raises, it catches, logs, and skips — **the opposite of a handler registered directly on the EventBus**, where exceptions propagate. The difference is deliberate: plugins come from third parties, and one broken plugin should not abort a whole run. -This adapter follows the plugin hook order. It catches exceptions from individual interception hooks, logs them, and skips them. To deny a call, return the correct decision. The other app hooks follow their own error contracts. +To deny a call, return the corresponding decision. Other app hooks follow their own error rules. -See [Hooks](/docs/app/hooks/) for plugin examples. For order and signatures, see [Hooks reference](/docs/reference/hooks/). EventBus is part of the base SDK installation. +For plugin examples, see [Hooks](/docs/app/hooks/). For ordering and signatures, see the [hooks reference](/docs/reference/hooks/#interception-through-pluginagenthooks). The EventBus is available in the base SDK install. -`framework.events` publishes commands, channel work, and user prompts. It uses `CommandChanged`, `WorkChanged`, and `PromptChanged` from `bub.application`. You subscribe and unsubscribe with the same `on()` and `off()` API. +`framework.events` is a separate bus that publishes app-level events: commands, channel work, and user input, through `CommandChanged`, `WorkChanged`, and `PromptChanged` from `bub.application`. Subscribing and unsubscribing are still `on()` and `off()`. The two buses are independent: `framework.events` knows nothing about model-call details, and `agent.events` knows nothing about channels. diff --git a/website/src/content/docs/docs/concepts/extension-points.mdx b/website/src/content/docs/docs/concepts/extension-points.mdx index da6d6b48e..8175faacd 100644 --- a/website/src/content/docs/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/docs/concepts/extension-points.mdx @@ -3,29 +3,92 @@ title: Extension points description: Decide whether your task needs a tool, an event handler, a skill, a plugin, or a channel. --- -Start with the behavior that you want to add. A new action needs a tool. A new place to receive messages needs a channel. You can combine these capabilities, but do not give them the same job. +import { Aside, Card, CardGrid, Steps } from '@astrojs/starlight/components'; -| You want to | Use | Where it belongs | +The previous three pages describe how Bub runs itself. This one describes where your addition belongs. + +First decide what behavior you are adding. A new action is a tool. A new place to receive messages is a channel. These capabilities stack, but do not let two of them do the same job. + +## Ask three questions {#three-questions} + +When you want Bub to do X, ask in order: + + +1. **Does X run code or reach an external system?** Yes → tool. No → next question. +2. **Is X a procedure, convention, or checklist?** Yes → skill. No → next question. +3. **Does X change how Bub itself behaves — how messages arrive, how they are processed, where they go?** Yes → plugin or channel. + + +If none match, you are most likely changing the kernel's model loop, which means assembling an Agent with the SDK directly. + +## Choose {#choose} + +| You want to | Use | Which layer | Start at | +| --- | --- | --- | --- | +| Let the agent read or change something | [Tool](/docs/sdk/tools/) | A general kernel capability. Pass it to the Agent, or register it in the app. | [Registering tools](/docs/app/tools/) | +| Watch or stop a model or tool call | [Event handler](/docs/concepts/events/#call-control) | The kernel's EventBus. App plugins use the matching interception hooks. | [Hooks](/docs/app/hooks/) | +| Change which history becomes model messages | [TapeContext](/docs/concepts/tape/#context) | Pass it to the Agent, or provide it through an app hook. | [Replacing storage and context](/docs/integrations/storage/) | +| Teach the agent a reusable procedure | [Skill](/docs/app/skills/) | The Bub app discovers and loads it. | [Skills](/docs/app/skills/) | +| Change how the app initializes or how messages are handled | [Plugin](/docs/app/plugins/) | `BubFramework` and its hooks. | [Plugins](/docs/app/plugins/) | +| Connect a messaging service | [Channel](/docs/app/channels/) | The app's inbound and outbound routing. | [Channels](/docs/app/channels/) | + +## Kernel and app {#kernel-and-app} + +The same capability usually has one entry in each layer. That is not duplication; it is two ways of assembling the same thing: + + + + `Agent(tools=[...], context=..., store=...)`. + + For services that manage their own lifecycle and embed Bub. No settings file, no plugin discovery. + + + `provide_tools`, `build_tape_context`, `provide_tape_store`. + + For distributing to others, or when it should work as soon as it is installed. Plugins can be discovered, configured, and reused. + + + +The deciding question is **who owns the lifecycle**. If the code lives in your service and you decide everything at startup, use the kernel constructor arguments. If it should take effect after someone runs `pip install`, write a plugin. + +## Tools and skills {#tools-and-skills} + +A tool runs Python and returns a result. A skill is a directory with a `SKILL.md` and optionally other files. Its instructions can tell the agent how to use a tool. + +The boundary between them gets confused often, so it is worth stating plainly: **a skill runs no code**. It is procedure written for the model — "read these files before changing code", "write the PR description with this template". Python functions mentioned in the instructions are yours to write and attach separately. + +| Dimension | Tool | Skill | | --- | --- | --- | -| Let the agent read or change something | A [tool](/docs/sdk/tools/). | A shared core capability. Pass it to Agent, or register it with the app. | -| Watch or intercept a model or tool call | An [event handler](/docs/concepts/events/). | The core EventBus. App plugins can use the matching interception hooks. | -| Change which history becomes model messages | A [TapeContext](/docs/concepts/tape/#context). | Pass it to Agent, or provide it through an app hook. | -| Teach a procedure that the agent can reuse | A [skill](/docs/app/skills/). | The Bub app discovers it and loads it. | -| Change the app setup or the message processing | A [plugin](/docs/app/plugins/). | `BubFramework` and its hooks. | -| Connect a messaging service | A [channel](/docs/app/channels/). | The inbound and outbound routing of the app. | +| Content | A Python function with type hints | Markdown instructions plus supporting files | +| Who reads it | the model, calling it through a schema | the model, once in context, then following it | +| Effect | Performs an action, returns data | Changes how the model works | +| When loaded | The available toolset for each run | Discovered by the app and added to the prompt as needed | +| Distribution | Registered through a plugin | Lives in the workspace or ships with a package | + +A plain Agent uses only the tools and prompt sections you give it. The Bub app discovers skills and also reads `AGENTS.md`. See [Skills](/docs/app/skills/) and [Tools](/docs/app/tools/). + +The agent can also call a tool through a [text command](/docs/sdk/tools/#call-a-tool-without-a-model), or have CodeMode write Python that calls it. Every path runs the same tool implementation — **one function, three triggers** — so a tool behaves the same whichever path invokes it. + +## Messages and state {#messages-and-state} + +A channel receives an inbound envelope and sends outbound ones. The app uses envelopes to carry content, routing, and session identity. All the hooks share the current turn's `TurnState` dictionary. -## Tools and skills +The three have clearly separate jobs: -A tool runs Python and returns a result. A skill is a directory with a `SKILL.md` file and, optionally, more files. Its instructions can tell the agent how to use tools. You write the Python functions that the instructions refer to, and you add them separately. +- An **envelope** carries "where from, where to", across a process boundary. +- **`TurnState`** carries "what this turn remembers", living for a single turn. +- A **tape** carries "what already happened", across turns. -A plain Agent uses the tools and prompt sections that you give it. The Bub app discovers skills and reads `AGENTS.md`. See [Skills](/docs/app/skills/) and [Tools](/docs/app/tools/). +To add a new message source, implement a channel that produces envelopes. To add behavior at a specific stage, implement a plugin hook that reads `TurnState`. To leave a long-lived record, write to the tape. -The agent can also reach a tool through a [text command](/docs/sdk/tools/#call-a-tool-without-a-model), or through Python that the model writes with CodeMode. All these paths use the same tool implementation. +[Turn](/docs/concepts/turn/) prepares the state and prompt before calling the agent. If you use the SDK directly, you hand the prompt and state to the Agent yourself. -## Messages and state +## Keep one owner per job {#one-owner} -A channel delivers an inbound envelope and sends outbound envelopes. The app uses these envelopes for the content, the routing, and the session identity. Its hooks share a mutable `TurnState` dictionary for the current turn. +This is the most common extension-design mistake, and the cost is that nobody can later say which place is authoritative. -The [a turn](/docs/concepts/turn/) of the app prepares that state and the prompt before it calls the agent. A direct SDK caller gives the prompt and the state to Agent. +- If you already block refunds in `before_tool_call`, do not repeat the check inside the tool function. +- If a skill already documents the commit convention, do not duplicate it in `AGENTS.md`. +- If `provide_tape_context` already selects the context, do not rebuild history again in `build_prompt`. -Choose the dependencies for the capabilities that you need. Then choose the interface for the host. A Python service can reuse app plugins and keep its own request handling and streams. [Reusing plugins](/docs/sdk/reuse-plugins/) shows how to use components directly and how the framework hooks fit into an embedded host. +A simple test: **if you deleted this logic, would another path fail to cover it?** If two paths both cover it, delete one. diff --git a/website/src/content/docs/docs/concepts/index.mdx b/website/src/content/docs/docs/concepts/index.mdx index 2d8162413..fbe060ece 100644 --- a/website/src/content/docs/docs/concepts/index.mdx +++ b/website/src/content/docs/docs/concepts/index.mdx @@ -1,45 +1,93 @@ --- title: Overview -description: The four concepts that both ways of using Bub share, the layer each one lives in, and where to read about it. +description: Why Bub splits into a kernel and plugins, and what the four concepts each own in one interaction. --- -Bub has four concepts. A **turn** carries one message through the app. A **tape** stores the conversation. **Events** report what the loop does. An **extension point** is a place where new behavior goes. Your own Agent and the Bub app use all four. +## Background {#background} -Read the pages in any order. Each page starts with the short answer, then the details. +Bub used to keep everything in the main package. Every new message channel meant another class under `bub.channels` plus a batch of tools or skills, and every new capability meant another switch. The author recorded the call at the time in [Why we rewrote Bub](/posts/why-rewrite-bub/): -## Layers +> If we kept going in that direction, Bub would eventually become another Nanobot, or even another OpenClaw. [...] Most users only need one or two providers and one or two channels, yet they still end up staring at a wall of settings and switches. Even if most of them are disabled by default, the complexity is still there, and users still get lost in it. -`core.Agent` takes a model, tools, prompt sections, and a tape store. You pass these components in yourself, so a small Python service needs only the core dependencies. +So the rewrite took the opposite direction: -`BubAgent` adds the app defaults: settings, skills, commands, tool selection, and steering. `BubFramework` manages plugins, message processing, and outbound routing around it. Use these parts when you want the behavior they provide. +> My answer is to split extra functionality out of the core and move to a model with a **carefully designed lightweight kernel** plus **freely vibe-coded feature plugins**. -`pip install bub` installs the kernel. `pip install "bub[app]"` installs both layers. The [installation guide](/docs/getting-started/install/) explains the dependency choices. +The kernel has to be stable, understandable by agents, and quality-controlled by maintainers. Plugins can be written however people like, even generated on the spot by an agent. One maintenance model is strict; the other is loose. -## Concepts +Which raises the question: **once the pieces are split, where do the boundaries go?** This section is those boundaries. The four pages after it expand one each. -| Concept | What it is | Read | -| --- | --- | --- | -| Turn | One inbound message, and all the work that it causes. | [Turn](/docs/concepts/turn/) | -| Tape | The stored conversation, and the part of it that the model reads. | [Tape](/docs/concepts/tape/) | -| Events | The bus that reports each step, and lets a handler change the next one. | [Events](/docs/concepts/events/) | -| Extension points | Where new behavior goes: a tool, a skill, a plugin, or a channel. | [Extension points](/docs/concepts/extension-points/) | +## The journey of one interaction {#journey} + +Take the most common interaction: you ask something in the terminal, and Bub answers. + +```mermaid +flowchart LR + A["message"] --> B["prepare"] --> C["loop"] --> D["wrap up"] --> E["reply"] + C -.-> T[("tape")] +``` + +**Prepare** decides which session the message belongs to, restores what the previous turn left behind, and turns the message into a prompt. **Loop** takes the prompt to the model, runs any tools the model asks for, feeds the results back into the context, and asks again until the model answers without asking for tools. **Wrap up** saves the result, packages it as an outbound message, and hands it to the channel. + +Of the three, only the middle one is general. It does not matter whether the message came from a terminal, Telegram, or anywhere else: the "model ⇄ tools" stretch is identical. The two stages around it both deal with where a message comes from and where the reply goes, and that is exactly the part that changes with deployment. + +That is where the line falls. The loop stays in the kernel; both ends belong to the app. + +## The four concepts {#concepts} + +Along that line, four concepts show up in order. The order is not arbitrary: each one answers a question the previous one left open. + +### Turn: gather both ends into named stages {#turn} + +If both ends belong to the app, then the whole stretch from receiving a message to sending a reply should have a clear shape. That is a **turn**: one inbound message, and all the work it causes. `BubFramework` cuts it into seven stages, each with a name and a matching hook. + +This follows one of the three principles the rewrite settled on: one-way dependencies. The app knows the kernel; the kernel does not know the app. The kernel reads no settings and recognizes no plugins. Hooks belong to the app: a plugin implements them, and `BubFramework` calls them. The same `core.Agent` therefore behaves identically in both layers, so what you verified through the SDK still holds inside the App. + +**Three words inside a turn need separating**: a turn can contain one **run** (one `agent.run()` call), and a run can contain several **steps** (one model call plus the tools it asks for). turn ⊃ run ⊃ step. -## Terms +To change how messages arrive or how replies leave, change this page. [Read Turn](/docs/concepts/turn/) -An **Agent** runs the model and tool loop. A **run** is one `agent.run()` call, and it can contain several **steps**. A **step** is one model call and the tools that it asks for. A **turn** is one inbound message and the run that answers it. +### Tape: split history from context {#tape} -A **tape** stores the conversation. Its **entries** are messages, tool calls, results, and events. An **anchor** marks the point where the context starts. A **handoff** writes a new anchor. The turn's **state** is a separate dictionary that tools and handlers share. +The loop needs a context, so the first question is where that context comes from. -The app adds three parts. **Plugins** provide Python hooks. **Skills** provide written instructions. **Channels** carry messages in and out. +The obvious approach is to keep a message array and append to it each round. Bub does not do that. This is the second principle, **append-only context**: every record is written to a **tape** without modifying earlier entries, and the model reads a slice of that tape rather than an ever-growing array. -## Design notes +The benefit of splitting them is direct. History keeps everything, so you can audit it later; the context selects only the entries worth reading, so requests stay short. And because recording only appends, changing your mind never corrupts what is already stored. -The kernel does not read settings or plugin hooks. Hooks belong to the app: a plugin implements them, and `BubFramework` calls them. The core therefore works the same in both layers. +The cost is a few new words. A tape **entry** is a message, tool call, tool result, event, or anchor. An **anchor** marks where the context starts, and a **handoff** writes a new anchor **without deleting** older entries — it is a switch for forgetting, not a switch for compressing. The turn's **state** is something else again: a plain dictionary shared within the run, never persisted. -History and context stay separate. The tape keeps every entry. The context selects the entries that the next model call reads. An anchor starts a new context and keeps the earlier entries, so you can read the full history and still send a short request. +To change how much the agent remembers, change this page. [Read Tape](/docs/concepts/tape/) -Each part has one job. A tool performs an action. A skill describes a procedure. A channel connects the app to a place where people work. These roles stay separate, so you know which part to change. +### Events: let the loop narrate each step {#events} -## Background +Once the loop is running, the host has one question left: how do I know what it is doing? + +Bub's answer is the third principle, **event-driven**. Every model call and every tool execution emits a start, an end, and a result. The host can watch a run, and can also rewrite a call before it happens, or stop it outright. + +The easiest thing to confuse here is the three kinds of "event": `AgentEvent` on the `EventBus` (you can intercept and change it), the `StreamEvent` given to the caller (read-only; it cannot change the run), and the `TapeEntry` written to the tape (readable afterwards; it does not affect the step in progress). Get the direction wrong and the behavior is wrong too. + +Logging, permissions, budgets, auditing, and progress bars are all just handlers attached to the bus. None of them requires touching the loop or the tools. [Read Events](/docs/concepts/events/) + +### Extension points: actions, procedures, connections {#extension-points} + +Last, back to the original question: you want to add something, so where does it go? + +This is where the remaining two principles land. **Tools are functions** — a type-hinted Python function is a tool, so adding an action is cheap. **Everything is a plugin** — capabilities can all be provided by plugins, and a plugin can be a tool, an event listener, a message channel, or a whole set of skills. + +But precisely because it can hold anything, the responsibilities need separating: a tool performs an action, a **skill** describes a procedure (instructions only, no code execution), a **channel** connects to where people work, and a **plugin** packages the earlier ones. The message unit passed between channels is an **envelope**; interjecting during a run is **steering**. + +Roles do not overlap, so you know which part to change. Conversely, Bub has no universal extension point that requires no other change — first decide whether you are adding an action, knowledge, or a connection. [Read Extension points](/docs/concepts/extension-points/) + +## Summary {#summary} + +The four concepts are the principles from [Core design](/docs/getting-started/#core-design), told a different way: + +| Concept | The question it answers | The principle behind it | +| --- | --- | --- | +| Turn | What does the app do, in order, after a message arrives? | One-way dependencies: the app knows the kernel, not the reverse | +| Tape | Where does history live, and what does the model read? | Append-only context: write, never modify; read a slice | +| Events | How do I watch, change, or stop a call? | Event-driven: start, end, and result are all emitted | +| Extension points | What should the thing I want to add become? | Tools are functions; everything is a plugin | -[Why we rewrote Bub](/posts/why-rewrite-bub/) and [Socialized evaluation and agent partnership](/posts/socialized-evaluation/) explain the earlier design. For the current interfaces, use the guides and the reference. +Read the pages in any order, but start here: the other four expand different cuts of the same journey. diff --git a/website/src/content/docs/docs/concepts/tape.mdx b/website/src/content/docs/docs/concepts/tape.mdx index dc4838d0a..332cf354d 100644 --- a/website/src/content/docs/docs/concepts/tape.mdx +++ b/website/src/content/docs/docs/concepts/tape.mdx @@ -1,13 +1,44 @@ --- title: Tape -description: Where Bub keeps the conversation, what the model reads on the next call, and what survives a run. +description: Where the conversation is stored, what the next model call reads, and what survives a run. --- -An agent needs two things. It needs a record of its work. It also needs a useful set of messages for the next model call. Bub keeps the record in a tape. It builds the model context from the entries that you select. Because these are separate, you can keep the full history and still send a short request. +import { Aside } from '@astrojs/starlight/components'; -`core.Agent` and `BubAgent` share this model. Their default stores and session names are different. +An agent needs two things. It needs a record of its work. It also needs a useful set of messages for the next model call. Bub puts the record in a tape and builds the model context from the entries you select. -## Storage +Keeping the two apart is the most useful decision in Bub: you can keep a complete history and still send a short request. This page covers how each side works and where the edges are. + +`core.Agent` and `BubAgent` share this model. Their default stores and session names differ. + +## One conversation, two readings {#history-and-context} + +Start with this distinction; everything below hangs off it. + +```mermaid +flowchart LR + subgraph store["store"] + E1["anchor
session/start"] + E2["message
user: where is order A123"] + E3["tool_call
lookup_order"] + E4["tool_result"] + E5["message
assistant: shipped"] + E6["event
loop.step"] + E7["anchor
order-reviewed"] + E8["message
user: and the shipping cost"] + E1 --- E2 --- E3 --- E4 --- E5 --- E6 --- E7 --- E8 + end + + E1 -.->|"kept"| A["full history
every entry readable"] + E7 ==>|"enters the context"| B["model reads
only after the anchor"] +``` + +- **History** is every entry in write order, append-only. Incident review, audit, and debugging read it. +- **Context** is a filtered slice, converted to Republic messages and handed to the model. The model never sees what was filtered out. + +The filter is `TapeContext`, and its default rule is "conversation entries after the last anchor". + +## Storage {#storage} A plain `Agent` creates an `InMemoryTapeStore` by default. That history lives as long as the store. To keep it between processes, pass a file store: @@ -20,9 +51,9 @@ from bub.core.store import FileTapeStore agent = Agent(store=FileTapeStore(Path("sessions"))) ``` -Each `agent.run(..., tape="customer-42")` uses the named tape in that store. Use the same name to continue a conversation. Use another name to keep conversations apart. `Agent` uses the name as given. The app has its own workspace and session naming. +Each `agent.run(..., tape="customer-42")` uses the tape named `customer-42` in that store. The same name continues the earlier conversation; a different name starts a separate one. `Agent` uses the name you give it as is. The app has its own workspace and session naming rules; see [app storage](#app-storage). -`agent.tape(name)` gives you a view of a tape outside a run. Its writes go straight to the store: +`agent.tape(name)` gives you the tape outside a run. Writes through it go straight to the store: ```python async def inspect_history() -> None: @@ -32,32 +63,29 @@ async def inspect_history() -> None: print(len(messages), "model messages;", len(entries), "stored entries") ``` -To use your own store, implement `TapeStore` or `AsyncTapeStore` from `bub.core.store`. Pass the instance as `Agent(store=my_store)`. +`read_messages()` applies the context rules; `query()` reads raw entries. One tape, two readings — the diagram above, in code. -| Method | What it does | -| --- | --- | -| `list_tapes()` | Return the names of the stored tapes. | -| `fetch_all(query)` | Return the entries that match the `TapeQuery`, with its filters and order. | -| `append(tape, entry)` | Append an entry to the named tape. | -| `reset(tape)` | Clear the named tape. | +To write your own store, implement `TapeStore` or `AsyncTapeStore` from `bub.core.store`. The store interface and `TapeQuery` filters are in the [Python API reference](/docs/reference/python-api/). Your store and your app handle persistence and coordination between processes; the [SQLAlchemy guide](/docs/integrations/storage/) shows a complete replacement. -Use synchronous methods for `TapeStore` and asynchronous methods for `AsyncTapeStore`. The agent adapts a synchronous store for async use. Your store and your app handle persistence and coordination between processes. +## Context {#context} -## Context +Tape entries are messages, tool calls, tool results, events, and anchors. Normal recording only appends. To clear history, call an explicit operation such as `Tape.reset()`. -Tape entries are messages, tool calls, tool results, events, and anchors. Normal recording only appends entries. To clear history, call an explicit operation such as `Tape.reset()`. - -The default `TapeContext` selects the conversation entries after the latest anchor. It then converts them to Republic messages: +The default `TapeContext` selects conversation entries after the last anchor, then converts them to Republic messages: | Entry | Model context | | --- | --- | -| `anchor` | The default query leaves it out. If a custom query selects anchors, the default selector converts them to assistant messages. | -| `message` | The saved message. | -| `tool_call` | An assistant message that carries the calls. | -| `tool_result` | Tool messages, paired with the calls before them. | -| Other events | The default selector leaves them out. | +| `anchor` | The default query excludes it. If a custom query selects an anchor, the default selector turns it into an assistant message. | +| `message` | The stored message. | +| `tool_call` | One assistant message carrying the calls. | +| `tool_result` | A tool message, paired with the earlier call. | +| Other events | The default selector skips them. | + +Note the last row. **Events do not enter the context by default**, so what you record on the tape and what the model sees are two different things. That is both a freedom and a trap: you can record plenty of useful information that the model never reads. + +### Cutting context with an anchor {#anchors} -An anchor marks a new starting point for the context. `handoff()` writes one and keeps the earlier entries: +An anchor marks a new starting point for the context. `handoff()` writes one, and earlier entries stay: ```python async def start_next_phase() -> None: @@ -67,38 +95,94 @@ async def start_next_phase() -> None: ) ``` -This `state` is saved on the anchor, before the starting point of the default query. To carry a summary into the next phase, put it in the next prompt or select it in your context strategy. To load values back into the run's mutable state, write your own loader. A new session gets a `session/start` anchor automatically. +The `state` here is stored on the anchor, before the default query's start. **To bring the summary into the next phase you must put it into the next prompt, or select it explicitly in your context strategy** — a handoff does not hand anything to the model by itself. + +This is the most common misunderstanding about handoff: it is a mechanism for **forgetting**, not for remembering. It decides what old entries become invisible; it does not compress them into something new. + +A new session gets a `session/start` anchor automatically, so the first run has a starting point too. -To choose another anchor, pass `context=TapeContext(...)` to `Agent`. You can also use your own `select(entries, context)` function. A selector returns Republic messages, or an awaitable of them. `default_tape_context()` includes tool exchanges. A bare `TapeContext` without `select` uses the simpler message-only conversion. Entries marked `context=False` are removed before either selector runs. +### Choosing another rule {#custom-selectors} + +To choose another anchor, pass `context=TapeContext(...)` to `Agent`. You can also supply your own `select(entries, context)` function: + +```python +from bub.core import Agent +from bub.core.context import default_tape_context +from bub.core.tape import TapeContext -When the agent recognizes a context-length error, it makes one automatic handoff. It then retries the prompt with the shorter context. If you need a summary of the earlier history, create it before the handoff. The retry can still fail if the request is too large. -## State +def select_recent_business_events(entries, context): + """Hand business events to the model, not just messages.""" + return default_tape_context().select(entries, context) -The `state` that you pass to `agent.run()` is a mutable dictionary. Tools, prompt sections, and event handlers share it during the run. Your app owns its persistence. Keep durable data in your own store, or write selected facts to the tape and read them back when you need them. -The default [TapeRecorder](/docs/concepts/events/#recording) saves conversation exchanges and selected lifecycle records. To keep other variables, custom events, or decisions, record them yourself. +agent = Agent(context=TapeContext(anchor=None, select=select_recent_business_events)) +``` + +A selector returns Republic messages, or an awaitable of them. `default_tape_context()` includes tool round-trips; a `TapeContext` without `select` uses a simpler message conversion. Entries with `context=False` are dropped before either the default or a custom selector runs. + +### When the context overflows {#context-overflow} + +When the agent recognizes a context-length error, it makes one automatic handoff and retries with the shorter context. + + + +To manage context against a budget on purpose, the [`bub-context-budget` plugin](/docs/sdk/reuse-plugins/) is a working example: it asks the model to call `tape.handoff` as the limit approaches. + +## State {#state} + +The `state` that you pass to `agent.run()` is a mutable dictionary. Tools, prompt sections, and event handlers share it during the run. + +It is **not** persisted. When the run ends, nothing owns the dictionary. Your app owns its persistence: keep durable data in your own store, or write selected facts to the tape and read them back when you need them. + +The default [TapeRecorder](/docs/concepts/events/#recording) saves conversation exchanges and selected lifecycle records. To keep other variables, custom events, or decisions, record them yourself — either in `state` for the duration of the run, or with `append_event` on the tape for the long term. + +| What you need to keep | Where it goes | +| --- | --- | +| A value computed during this run | `state`, discarded when the run ends | +| A business fact you must query later | an `event` entry on the tape | +| Structured data to read back next run | your own store, or an anchor's `state` plus explicit loading | +| A fully auditable conversation | the tape itself (TapeRecorder already does this) | + +## Forks {#forks} + +Each `agent.run()` opens a fork of its tape. When the stream closes, the fork appends its new entries to the parent tape. + +That holds on the error path too: an exception or an early cancellation still merges. A failed run can therefore leave useful partial history, which is valuable when debugging. + +```mermaid +flowchart LR + P[("parent tape
existing entries")] + P --> F["fork
the run writes here"] + F -->|"stream closes / raises / cancelled"| M["merged into the parent"] + F -.->|"merge_back=False"| D["this run's writes dropped"] + M --> P2[("parent tape
has the new entries")] +``` + +Pass `merge_back=False` and the fork's records are dropped on exit. This affects tape writes only: side effects a tool already produced, such as a sent request or a changed file, remain. **A fork isolates records, not side effects.** -## Forks +To manage a piece of work yourself, use `agent.session(name, merge_back=...)` or `tape.fork_tape(merge_back=...)`. Both merge on exit by default, including on an exception. -Each `agent.run()` opens a fork of its tape. When the stream closes, the fork appends its new entries to the parent tape. This also happens after an exception or an early cancellation. A failed run can therefore leave useful partial history. +To let two runs share a tape and see each other's finished work, chain them. Your app provides the session lock. The [FastAPI example](/docs/sdk/web-service/) shows locking within one process; multiple workers need separate coordination. -Pass `merge_back=False` to discard the records of the fork on exit. This affects tape writes only. Tool side effects, such as a sent request or a changed file, stay. +## Sidecars {#sidecars} -For a block of work that you control directly, use `agent.session(name, merge_back=...)` or `tape.fork_tape(merge_back=...)`. Both merge on exit by default, including exits with an exception. +A `TapeSidecar` stores related data in a separate tape that still belongs to the same tape conceptually. -If two runs share a tape and must see each other's completed work, serialize them. Your app provides the session lock. The [FastAPI example](/docs/sdk/web-service/) shows locking in one process. Multiple workers need coordination between processes. +A mounted sidecar forks, merges, and resets with the main tape. Its entries do not enter the main context, so they cannot crowd the model request. Pass one with `Agent(sidecars=...)` when your app needs it. -## Sidecars +The Bub app mounts a spill sidecar for large tool results. It keeps a short preview and a handle in the conversation, and the agent can then read parts of the result with `spill.read`. This is another way to apply "history can be large, context must be small": move the bulk aside instead of deleting it. -A `TapeSidecar` stores related data in a separate tape. A mounted sidecar follows the main tape through forks, merges, and resets. Its entries stay out of the main context. Pass sidecars to `Agent(sidecars=...)` when your app needs them. +If a sidecar fails during a fork merge or a tape reset, the failure is recorded separately and the main tape operation still completes — a sidecar is an add-on, and a broken one should not take the main flow down. The spill limits are in the [app configuration](/docs/app/configure/). -The Bub app mounts a spill sidecar for large tool results. It keeps a short preview and a handle in the conversation. The agent can then read parts of the result with `spill.read`. If a sidecar fails during a fork merge or a tape reset, the app records the failure separately. The main tape operation can still complete. The spill limits are in the [app configuration](/docs/app/configure/). +## App storage {#app-storage} -## App storage +`BubAgent` uses the store that `BubFramework` provides. The built-in app uses `FuzzyFileTapeStore` under the Bub home directory, normally `~/.bub/tapes/`. -`BubAgent` uses the store that `BubFramework` provides. The built-in app uses `FuzzyFileTapeStore` under the Bub home directory, normally `~/.bub/tapes/`. The tape name combines hashes of the workspace path and the session ID. The same session ID in two workspaces therefore has two separate histories. +The tape name combines hashes of the workspace path and the session ID. The same session ID in two workspaces therefore has two separate histories: using one session name in `~/work` and `~/scratch` will not show you what the other said. -To replace the store, an app uses `provide_tape_store`. To select the context, it uses `build_tape_context`. The [SQLAlchemy guide](/docs/integrations/storage/) shows this plugin route. For a direct SDK service, pass your implementation to `Agent(store=...)` as shown above. +To change the store, the app uses `provide_tape_store`. To choose context, it uses `build_tape_context`. The [SQLAlchemy guide](/docs/integrations/storage/) shows that plugin route. For a service that uses the SDK directly, pass the implementation to `Agent(store=...)` as shown in [Storage](#storage). -The built-in app restores selected session settings from tape records. Other turn-state values need their own persistence. +The built-in app restores some session settings from tape records. Any other per-turn state is yours to persist. diff --git a/website/src/content/docs/docs/concepts/turn.mdx b/website/src/content/docs/docs/concepts/turn.mdx index 922ceb5a5..85630647b 100644 --- a/website/src/content/docs/docs/concepts/turn.mdx +++ b/website/src/content/docs/docs/concepts/turn.mdx @@ -1,46 +1,81 @@ --- title: Turn -description: One inbound message, from the session lookup to the reply that leaves the app. +description: One inbound message, from the session lookup to the reply that leaves the app, and where each stage leaves room for a plugin. --- -A turn is one inbound message, and all the work that it causes. `BubFramework` handles a turn: it finds the session, loads the state, builds the prompt, runs the model, and delivers the reply. Each stage has a hook, so a plugin can change that stage. +import { Aside, Steps } from '@astrojs/starlight/components'; -A turn belongs to `bub[app]`. A direct `core.Agent.run()` call has no turn. The loop starts with the prompt, state, and tape that you give it. +A turn is one inbound message, and all the work that it causes. `BubFramework` handles a turn: it finds the session, loads the state, builds the prompt, runs the model, and delivers the reply. Each stage has a hook, so a plugin can add to one stage or replace it. -## Stages +A turn belongs to `bub[app]`. A direct `core.Agent.run()` call has no turn — the loop starts with the prompt, state, and tape that you give it. This page covers the layer between a channel and that loop. -`framework.process_inbound(message)` runs these stages in order: +## Why a turn exists {#why-a-turn} -| Stage | What it does | -| --- | --- | -| `resolve_session` | Select the session ID for the inbound message. | -| `load_state` | Collect the turn state from the hook implementations. | -| `build_prompt` | Turn the inbound content into a prompt. | -| `run_model` / `run_model_stream` | Produce the model output, as a final result or as a stream. | -| `save_state` | Finish the work of the model stage. This stage runs in its `finally` block. | -| `render_outbound` | Turn the result into outbound envelopes. | -| `dispatch_outbound` | Deliver each outbound envelope. | +With just a model loop, every call site has to answer the same questions again: which session is this message from? Should last round's state come along? Where does the reply go? -The built-in plugin supplies the app defaults. Other plugins can add behavior or replace it, as each hook's contract allows. The [hooks reference](/docs/reference/hooks/) defines the first-result hooks, the combined results, and the priority rules. +A turn lifts those out of tool and model code and fixes them into named stages. The payoff is concrete: -## Agent call +- **Every piece can be replaced**, but the replacement points have names, so you do not fork the whole flow. +- **The failing stage is explicit**: `on_error(stage=...)` receives its name. +- **One message is one transaction.** State loads at the start and saves at the end, and a failure in between has a defined cleanup. -The built-in `run_model_stream` hook calls `BubAgent.run_stream()`. `BubAgent` uses the core model and tool loop. It adds settings, skills, commands, steering, and the app tool policies. +## Stages {#stages} -The framework converts between streaming and non-streaming hook results. `process_inbound(..., stream_output=True)` lets the channel router read the output as it arrives. It returns a complete `TurnResult` when the stream ends. +`framework.process_inbound(message)` runs these seven stages in order. -A plugin can replace `run_model` and change this behavior. For example, an echo plugin can return text without an agent run. Core events and tape records occur only when the chosen implementation uses the core agent. + -## Resources and failures +1. **`resolve_session`**: select the session ID for the inbound message. The first non-empty result wins; when none is returned, a default is built from the channel and chat identifiers. -Use `async with framework.running():` when your hooks provide stores or other app resources. This opens the resources and releases them on exit. The built-in per-message lifespan opens during state loading and closes in `save_state`. +2. **`load_state`**: collect state fragments from the hook implementations and merge them into one `TurnState` dictionary. Lower priority merges first, and higher priority overrides. -`save_state` runs when execution reaches the model stage. It also runs when that stage raises an exception. A failure in session resolution, state loading, or prompt construction occurs before the `finally` block, so it does not run `save_state`. If your plugin gets resources in those earlier stages, release them yourself. +3. **`build_prompt`**: turn the inbound content into a prompt. The first result wins; when no hook provides one, the inbound content is used as is. -The built-in `save_state` closes the per-message lifespan. The app saves selected settings in a different place. To save other state, add your own persistence policy. +4. **`run_model` / `run_model_stream`**: produce the model output, as a final result or as a stream. The built-in implementation calls `BubAgent.run_stream()`, which enters the loop described below. -If the model stage raises an unhandled exception, the framework runs `save_state`, then skips rendering and dispatch. The outer error path calls `on_error(stage="turn", ...)` and raises the exception again. The built-in error observer can send an error reply through outbound dispatch. A failure in one `on_error` observer does not stop the other observers. +5. **`save_state`**: finish the work of the model stage. This stage runs in its own `finally` block, so an exception in the previous stage still reaches it. + +6. **`render_outbound`**: turn the result into outbound envelopes. Results from several hooks are concatenated; when all are empty, one default reply is used. + +7. **`dispatch_outbound`**: actually deliver each outbound envelope, through its channel. + + + +The first three stages prepare input, one stage produces output, and the last three send it out. Only stage 4 runs for long — the others wrap it on both sides. + +The built-in plugin supplies the default implementation of these seven stages. Other plugins can add behavior or replace it, as each hook's contract allows. The [hooks reference](/docs/reference/hooks/#turn-hooks) defines the first-result hooks, the combined results, and the priority rules. + +## What crosses the stages {#what-crosses-stages} + +Exactly three things pass from stage to stage. Together they are the turn's complete internal state: + +| Value | Type | Written by | Read by | +| --- | --- | --- | --- | +| `session_id` | `str` | `resolve_session` | every later stage, and `TurnResult` | +| `state` | `TurnState` | the `load_state` fragments; tools and handlers also mutate it during the run | prompt building, the model call, tools, `save_state` | +| `prompt` | `list[UserContent]` | `build_prompt` | the model stage | -## Next steps +`state` is the one most easily overlooked. It is a plain dictionary that lives from loading to saving, and tools and event handlers receive that same object. To keep anything across turns you must write it out in `save_state` and read it back in `load_state` — the built-in app persists only selected settings. The [tape state section](/docs/concepts/tape/#state) covers where durable data belongs. + +## Agent call {#agent-call} + +The built-in `run_model_stream` hook calls `BubAgent.run_stream()`. `BubAgent` uses the core model and tool loop, plus the app defaults: settings, skills, commands, steering, and the app tool policies. + +The framework converts between streaming and non-streaming hook results, so a plugin that implements only one of them still works. `process_inbound(..., stream_output=True)` lets the channel router read the output as it arrives, and still returns a complete `TurnResult` when the stream ends. + +```python +result = await framework.process_inbound(message, stream_output=True) +print(result.session_id, len(result.outbounds)) +``` + +Replacing `run_model` changes this step. An echo plugin, for example, can return text without creating an agent run — in which case no core events fire and no tape records are written. **Core events and tape records occur only when the chosen implementation actually uses the core agent**, which makes it the most direct way to tell how far a turn travelled. + +## Resources and failures {#resources-and-failures} + + + +If the model stage raises an unhandled exception, the framework runs `save_state`, then skips rendering and dispatch. The outer error path calls `on_error(stage="turn", ...)` and raises the exception again. The built-in error observer can send an error reply through outbound dispatch. A failure in one `on_error` observer does not stop the other observers. -Use [plugin setup](/docs/app/plugins/) to register your implementation. For common changes, see [Hooks](/docs/app/hooks/). For exact signatures and ordering, see [Hooks reference](/docs/reference/hooks/). +Use `async with framework.running():` when your hooks provide stores or other app 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`. diff --git a/website/src/content/docs/docs/getting-started/index.mdx b/website/src/content/docs/docs/getting-started/index.mdx index 535a1f723..d7b1f50e2 100644 --- a/website/src/content/docs/docs/getting-started/index.mdx +++ b/website/src/content/docs/docs/getting-started/index.mdx @@ -32,7 +32,7 @@ Which one should you use? The two are not exclusive. When you need to, you can even [reuse the App inside the SDK](/docs/sdk/reuse-plugins/#reuse-bubagent). -## Core design +## Core design {#core-design} Bub is built on being simple, composable, and extensible. That means you can freely combine Bub's capabilities, or build your own agent on top of it: diff --git a/website/src/content/docs/zh-cn/docs/concepts/events.mdx b/website/src/content/docs/zh-cn/docs/concepts/events.mdx index 01afece7a..23a85247a 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/events.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/events.mdx @@ -3,19 +3,42 @@ title: 事件 description: 看一次运行,改掉或叫停模型与工具调用,并记录你需要的事件。 --- +import { Aside } from '@astrojs/starlight/components'; + Agent 事件能让你看到一次运行,也能让你在模型或工具调用之前做决定。每个 Agent 都会建一个 `EventBus`,并挂上一个 `TapeRecorder`。你通过 `agent.events` 添加处理器。 -三类信息,三种用途: +事件是 Bub 里唯一的横切关注点。日志、权限、预算、审计、UI 进度,全都可以只写成处理器,不用改动循环和工具。 -| 类型 | 用途 | 怎么读 | -| --- | --- | --- | -| `AgentEvent` | 运行的生命周期,以及调用拦截。 | `agent.events.on(...)` | -| `StreamEvent` | 给调用方的输出,比如文本和工具进度。 | 迭代 `agent.run()` 返回的流。 | -| `TapeEntry` | 存下来的对话和部分运行记录。 | 查询 [tape](/zh-cn/docs/concepts/tape/)。 | +## 三类信息 {#three-kinds} + +"事件"这个词在 Bub 里指三个不同的东西,混起来最容易出 bug。先分清: + +| 类型 | 用途 | 怎么读 | 谁看 | +| --- | --- | --- | --- | +| `AgentEvent` | 运行的生命周期,以及调用拦截。 | `agent.events.on(...)` | 宿主代码和插件 | +| `StreamEvent` | 给调用方的输出,比如文本和工具进度。 | 迭代 `agent.run()` 返回的流 | 直接调用 agent 的代码 | +| `TapeEntry` | 存下来的对话和部分运行记录。 | 查询 [tape](/zh-cn/docs/concepts/tape/) | 之后运行时的模型 | + +三者的关系和职责: + +```mermaid +flowchart LR + loop["agent 循环"] + loop -->|"emit"| bus["EventBus"] + bus --> rec["TapeRecorder
(内置,第一个)"] + bus --> yours["你的处理器"] + loop -->|"yield"| stream["StreamEvent 流"] + rec --> tape[("tape 条目")] + stream --> caller["调用方"] + yours -->|"改 decision / 写 tape"| loop + tape -->|"下次运行读上下文"| loop +``` + +注意箭头方向。`StreamEvent` 是**单向出口**,你只能消费它,不能借它改变运行;要改变运行,得用 `AgentEvent` 的处理器。反过来,`TapeEntry` 是**事后**的,写进去不影响当前这一步。 循环控制执行,并在固定位置调用事件处理器,然后才往下走。TapeRecorder 保存选定的往返和生命周期记录。其他你要留的事件,由自己的处理器记录。 -## 处理器 +## 处理器 {#handlers} 下面用的是 [SDK 指南](/zh-cn/docs/sdk/)里的 `agent`: @@ -35,15 +58,35 @@ def report_step(event: StepFinished) -> None: agent.events.on(StepFinished, report_step) ``` -要移除,调 `agent.events.off(StepFinished, report_step)`。注册到基类上的处理器,也会收到子类事件。注册到 `AgentEvent` 上的处理器,会收到派发给它的所有 agent 事件。 +两种注册方式等价:装饰器适合新写的函数,直接传适合已有函数。要移除,调 `agent.events.off(StepFinished, report_step)`。 + +注册到基类上的处理器,也会收到子类事件。所有 agent 事件都继承 `AgentEvent`,所以注册到 `AgentEvent` 上的处理器会收到派发给它的全部事件——适合做统一的日志或计时,但要小心体积。 + +### 分发规则 {#delivery} + +四条规则决定了处理器能不能指望顺序: -每次派发,匹配的处理器按注册顺序执行。总线会等一个异步处理器做完,再调下一个。处理器返回什么,总线都不看。异常会抛给 `emit()` 的调用方,可能让整次运行失败。如果希望某个观察器出错后工作照旧往下走,就在观察器内部把异常接住。 +1. **按注册顺序执行。** 每次派发,匹配的处理器依次执行。 +2. **等异步处理器做完。** 总线会 await 一个异步处理器,再调下一个。 +3. **不看返回值。** 处理器返回什么,总线都忽略。要影响运行,得改事件对象的字段。 +4. **异常向上抛。** 处理器抛出的异常会抛给 `emit()` 的调用方,可能让整次运行失败。 + +第 4 条容易踩坑。观察性质的处理器应该自己接住异常: + +```python +@agent.events.on(ToolCallFinished) +async def audit_tool(event: ToolCallFinished) -> None: + try: + await send_to_audit_log(event.call.tool, event.result) + except Exception: + logger.warning("audit failed tool={}", event.call.tool) +``` -这个顺序只管一次派发。并发的工具调用或两个运行,可能同时发事件。处理器里共享的状态,仍要自己协调。 +另一个边界:**顺序只管一次派发**。并发的工具调用或两个运行,可能同时发事件。处理器里共享的状态,仍要自己协调。 -## 调用控制 +## 调用控制 {#call-control} -要叫停一次调用,给事件的 `decision` 字段赋值: +要叫停一次调用,给事件的 `decision` 字段赋值。这是事件机制里唯一能改写循环行为的地方。 ```python from bub.core.events import ToolCallDecision, ToolCallStarted @@ -55,9 +98,23 @@ def require_refund_permission(event: ToolCallStarted) -> None: event.decision = ToolCallDecision.deny("Refunds require approval.") ``` -`deny()` 跳过工具,给出一个工具错误。`replace(result)` 跳过工具,直接给出结果。两者都会让这个 `ToolCallStarted` 停止往后传。执行器仍会为结果发出 `ToolCallFinished`。模型循环把这个错误当成工具结果交给模型。直接命令调用则把错误抛给调用方。 +三种决断,效果各不相同: + +| 决断 | 工具跑了吗 | 模型看到什么 | 后续 | +| --- | --- | --- | --- | +| `deny(message)` | 没跑 | 一个工具错误 | 本次派发停止 | +| `replace(result)` | 没跑 | 你给的结果 | 本次派发停止 | +| 改 `event.call` | 用新参数跑 | 真实结果 | 继续派发 | + +`deny()` 跳过工具,给出一个工具错误。`replace(result)` 跳过工具,直接给出结果。两者都会让这个 `ToolCallStarted` 停止往后传。执行器仍会为结果发出 `ToolCallFinished`。模型循环把这个错误当成工具结果交给模型;直接命令调用则把错误抛给调用方。 + +要改参数再接着跑,用 `dataclasses.replace(event.call, arguments=...)` 换掉 `event.call`。注意这是替换而不是原地修改,事件对象本身是 frozen 的。 + +要在模型调用之前就结束,在 `LLMCallStarted` 上设 `event.decision = LLMCallDecision.finish(text)`。这会叫停后续处理器,也不去调供应商。被跳过的供应商调用不会发 `LLMCallFinished`——**你的计时和计费逻辑不能假设两个事件一定成对出现**。 + +### 事件总表 {#event-reference} -要改参数再接着跑,用 `dataclasses.replace(event.call, arguments=...)` 换掉 `event.call`。要在模型调用之前就结束,在 `LLMCallStarted` 上设 `event.decision = LLMCallDecision.finish(text)`。这会叫停后续处理器,也不去调供应商。被跳过的供应商调用不会发 `LLMCallFinished`。 +以下是全部 agent 事件。签名和完整字段见 [Python API 参考](/zh-cn/docs/reference/python-api/)。 | 事件 | 什么时候发 | 你能做什么 | | --- | --- | --- | @@ -73,21 +130,27 @@ def require_refund_permission(event: ToolCallStarted) -> None: | `ChatFinished` | 这一步的往返可以记录了。 | 读这一次往返。 | | `CommandFinished` | 直接命令完成或失败。 | 读命令、状态和输出。 | -命令提示词直接跑工具,会发出工具事件和 `CommandFinished`。见[命令](/zh-cn/docs/sdk/tools/#不经过模型调用工具)。 +`MessagesPending` 值得单独说一句:它是**运行中插话**的入口。`StepFinished` 的 `next_prompt` 设置下一步的用户消息,`MessagesPending` 在构建请求的最后一刻追加消息。steering 就是接到这两个点上实现的——用户在模型思考时补的那句话,不需要等下一次运行。 + +命令提示词直接跑工具,会发出工具事件和 `CommandFinished`。见[不经过模型调用工具](/zh-cn/docs/sdk/tools/#call-a-tool-without-a-model)。 所有 agent 事件都带本轮的 `state`。从 `TapeEvent` 派生的事件还带当前 tape。工具事件在调用有上下文时,可以从 `event.call.context` 取到。 -## 记录 +## 记录 {#recording} + +Agent 在你的处理器之前注册 TapeRecorder。它记录 `RunStarted`、`RunFinished`、`StepStarted`、`StepFinished`、`ChatFinished` 和 `CommandFinished`。 -Agent 在你的处理器之前注册 TapeRecorder。它记录 `RunStarted`、`RunFinished`、`StepStarted`、`StepFinished`、`ChatFinished` 和 `CommandFinished`。`ChatFinished` 这个处理器写入消息和工具往返,后面的模型调用就靠它拿上下文。 +其中 `ChatFinished` 最关键:这个处理器写入消息和工具往返,后面的模型调用就靠它拿上下文。换句话说,**TapeRecorder 不是可选的日志,它是上下文的生产者**——把 tape 上的往返删掉,下一次运行就读不到之前发生了什么。 -记录是正常流程的一部分。它写进你配置的存储,没另外指定就写在内存里。记录和 tracing 的配置无关。 +记录是正常流程的一部分。它写进你配置的存储,没另外指定就写在内存里。记录和 tracing 的配置无关:你可以关掉 tracing,记录照旧。 -要存自定义事件,加一个处理器把它写进 tape。要改请求或结果,用调用拦截事件。`ChatFinished` 的观察器运行时,默认记录器已经把这次往返存好了。 + -## 自定义事件 +## 自定义事件 {#custom-events} -定义一个继承 `AgentEvent` 的 dataclass。处理器需要 tape 时,就继承 `TapeEvent`。下面这个例子记一条业务事件: +定义一个继承 `AgentEvent` 的 dataclass。处理器需要 tape 时,就继承 `TapeEvent`。 ```python from dataclasses import dataclass @@ -112,14 +175,24 @@ async def mark_reviewed() -> None: ) ``` -`emit()` 在处理器跑完后返回事件。自定义的可变事件可以把结果带回发出方。结果一确定就该停止派发时,覆写它的 `stopped` 属性。工具可以通过 `context.events` 发事件,用的是同一份本轮状态。 +自定义事件继承了三个能力:走同一套分发规则、拿到本轮的 state 和 tape、可以被拦截。 + +`emit()` 在处理器跑完后返回事件。自定义的可变事件可以把结果带回发出方。结果一确定就该停止派发时,覆写它的 `stopped` 属性——内置的 `ToolCallStarted` 就是这么实现 `deny` 和 `replace` 提前终止的。 + +工具可以通过 `context.events` 发事件,用的是同一份本轮状态。 + +## 应用插件 {#app-plugins} + +内核用事件,应用用 hook。两者之间的桥是 `AgentPlugins`。 + +它把四个 hook 接到调用事件上:`before_llm_call`、`after_llm_call`、`before_tool_call`、`after_tool_call`。它把 `continue_prompt` 接到 `StepFinished`,把 steering inbox 接到 `MessagesPending`。插件按 hook 的要求返回值,适配器去改对应的事件。 -## 应用插件 +`BubAgent` 用的就是这个适配器,核心 Agent [也可以这么用](/zh-cn/docs/sdk/reuse-plugins/#other-hooks)。 -`AgentPlugins` 把四个 hook 接到调用事件上:`before_llm_call`、`after_llm_call`、`before_tool_call`、`after_tool_call`。它把 `continue_prompt` 接到 `StepFinished`,把 steering inbox 接到 `MessagesPending`。插件按 hook 的要求返回值,适配器去改对应的事件。`BubAgent` 用的就是这个适配器,核心 Agent [也可以这么用](/zh-cn/docs/sdk/reuse-plugins/#其他-hooks)。 +这个适配器遵循插件 hook 的顺序。单个拦截 hook 抛异常时,它接住、记日志、跳过——**和直接注册到 EventBus 的处理器相反**,那里异常会向上传播。这个差异是有意的:插件来自第三方,不该让一个坏插件中断整次运行。 -这个适配器遵循插件 hook 的顺序。单个拦截 hook 抛异常时,它接住、记日志、跳过。要拒绝一次调用,就返回对应的 decision。其他应用 hooks 按各自的出错规则处理。 +要拒绝一次调用,就返回对应的 decision。其他应用 hooks 按各自的出错规则处理。 -插件示例见[Hooks](/zh-cn/docs/app/hooks/)。顺序和签名见[Hooks 参考](/zh-cn/docs/reference/hooks/)。EventBus 在基础 SDK 安装里就有。 +插件示例见 [Hooks](/zh-cn/docs/app/hooks/)。顺序和签名见 [Hooks 参考](/zh-cn/docs/reference/hooks/#interception-through-pluginagenthooks)。EventBus 在基础 SDK 安装里就有。 -`framework.events` 发布命令、渠道工作和用户输入,用的是 `bub.application` 里的 `CommandChanged`、`WorkChanged` 和 `PromptChanged`。订阅和取消订阅,还是 `on()` 和 `off()`。 +`framework.events` 是另一个总线,发布应用层的事件:命令、渠道工作和用户输入,用 `bub.application` 里的 `CommandChanged`、`WorkChanged` 和 `PromptChanged`。订阅和取消订阅,还是 `on()` 和 `off()`。它和 agent 事件总线互相独立——`framework.events` 不知道模型调用的细节,`agent.events` 不知道渠道的存在。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx index 03c879bde..02f78ff52 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/extension-points.mdx @@ -3,29 +3,92 @@ title: 扩展点 description: 判断你的任务需要工具、事件处理器、技能、插件,还是渠道。 --- -先想清楚你要加什么行为。加一个新动作,用工具。加一个新的收消息的地方,用渠道。这些能力可以叠着用,但别让它们干同一件事。 +import { Aside, Card, CardGrid, Steps } from '@astrojs/starlight/components'; -| 你想 | 用 | 它在哪一层 | -| --- | --- | --- | -| 让 agent 读或改某个东西 | [工具](/zh-cn/docs/sdk/tools/)。 | 内核的通用能力。传给 Agent,或者在应用里注册。 | -| 看住或拦下模型、工具调用 | [事件处理器](/zh-cn/docs/concepts/events/)。 | 内核的 EventBus。应用插件可以用对应的拦截 hooks。 | -| 改哪段历史变成模型消息 | [TapeContext](/zh-cn/docs/concepts/tape/#context)。 | 传给 Agent,或者通过应用 hook 提供。 | -| 教 agent 一套可复用的做法 | [技能](/zh-cn/docs/app/skills/)。 | Bub 应用负责发现和加载。 | -| 改应用怎么初始化,或者消息怎么处理 | [插件](/zh-cn/docs/app/plugins/)。 | `BubFramework` 和它的 hooks。 | -| 接一个消息服务 | [渠道](/zh-cn/docs/app/channels/)。 | 应用的入站和出站路由。 | +前面的三页讲 Bub 自己怎么运转。这一页讲你想加东西时,它该放进哪。 + +先想清楚你要加的是什么行为。加一个新动作,用工具。加一个新的收消息的地方,用渠道。这些能力可以叠着用,但别让它们干同一件事。 + +## 先问三个问题 {#three-questions} + +遇到“我要让 Bub 做 X”时,按顺序问: + + +1. **X 需要执行代码或访问外部系统吗?** 需要 → 工具。不需要 → 下一题。 +2. **X 是一套做法、约定或检查清单吗?** 是 → 技能。不是 → 下一题。 +3. **X 要改变 Bub 自己的行为(消息怎么进来、怎么处理、发到哪)吗?** 要 → 插件或渠道。 + + +三步都没命中,那多半是“改内核的模型循环”,属于直接用 SDK 组装 Agent 的范畴。 + +## 选择表 {#choose} + +| 你想 | 用 | 它在哪一层 | 动手看 | +| --- | --- | --- | --- | +| 让 agent 读或改某个东西 | [工具](/zh-cn/docs/sdk/tools/) | 内核的通用能力。传给 Agent,或者在应用里注册。 | [应用注册工具](/zh-cn/docs/app/tools/) | +| 看住或拦下模型、工具调用 | [事件处理器](/zh-cn/docs/concepts/events/#call-control) | 内核的 EventBus。应用插件可以用对应的拦截 hooks。 | [Hooks](/zh-cn/docs/app/hooks/) | +| 改哪段历史变成模型消息 | [TapeContext](/zh-cn/docs/concepts/tape/#context) | 传给 Agent,或者通过应用 hook 提供。 | [换存储与上下文](/zh-cn/docs/integrations/storage/) | +| 教 agent 一套可复用的做法 | [技能](/zh-cn/docs/app/skills/) | Bub 应用负责发现和加载。 | [技能](/zh-cn/docs/app/skills/) | +| 改应用怎么初始化,或者消息怎么处理 | [插件](/zh-cn/docs/app/plugins/) | `BubFramework` 和它的 hooks。 | [插件](/zh-cn/docs/app/plugins/) | +| 接一个消息服务 | [渠道](/zh-cn/docs/app/channels/) | 应用的入站和出站路由。 | [渠道](/zh-cn/docs/app/channels/) | + +## 内核层与应用层 {#kernel-and-app} + +同一个能力,往往在两层各有一个入口。这不是重复,而是两种组装方式: + + + + `Agent(tools=[...], context=..., store=...)`。 + + 适合自己管理生命周期、把 Bub 嵌进已有服务。没有配置文件,没有插件发现。 + + + `provide_tools`、`build_tape_context`、`provide_tape_store`。 -## 工具与技能 + 适合分发给别人用,或者希望装上去就生效。插件可以被发现、被配置、被复用。 + + -工具跑 Python,返回结果。技能是一个目录,里面有 `SKILL.md`,还可以有别的文件。它的指令可以告诉 agent 怎么用工具。指令里提到的 Python 函数,你自己写,再另外挂上去。 +选择的标准是**谁来控制生命周期**。代码在你的服务里、启动时由你决定一切,就用内核层的构造函数参数;希望别人 `pip install` 之后自动生效,就写成插件。 -普通的 Agent 只用你给的工具和提示词片段。Bub 应用会去发现技能,也会读 `AGENTS.md`。见 [技能](/zh-cn/docs/app/skills/)和[工具](/zh-cn/docs/app/tools/)。 +## 工具与技能 {#tools-and-skills} -agent 也可以用一条[文本命令](/zh-cn/docs/sdk/tools/#不经过模型调用工具)调工具,或者用 CodeMode 让模型写 Python 来调。这几条路走的是同一个工具实现。 +工具跑 Python,返回结果。技能是一个目录,里面有 `SKILL.md`,还可以有别的文件。它的指令可以告诉 agent 怎么用工具。 -## 消息与状态 +两者的边界经常被弄混,值得说清楚:**技能不执行代码**。它是写给模型看的做法说明——"改代码前先读哪些文件""提 PR 时按这个模板写"。指令里提到的 Python 函数,你自己写,再另外挂上去。 + +| 维度 | 工具 | 技能 | +| --- | --- | --- | +| 内容 | Python 函数 + 类型标注 | Markdown 指令 + 附属文件 | +| 谁读 | 模型通过 schema 调用 | 模型读进上下文后遵循 | +| 效果 | 执行动作、返回数据 | 改变模型的做法 | +| 何时加载 | 每次运行的可用工具集 | 应用发现后按需拼进提示词 | +| 分发 | 随插件注册 | 放在工作区或随包分发 | + +普通的 Agent 只用你给的工具和提示词片段。Bub 应用会去发现技能,也会读 `AGENTS.md`。见[技能](/zh-cn/docs/app/skills/)和[工具](/zh-cn/docs/app/tools/)。 + +agent 也可以用一条[文本命令](/zh-cn/docs/sdk/tools/#call-a-tool-without-a-model)调工具,或者用 CodeMode 让模型写 Python 来调。这几条路走的是同一个工具实现——**同一个函数,三种触发方式**,所以工具的行为在哪条路上都一样。 + +## 消息与状态 {#messages-and-state} 渠道收一个入站 envelope,发出站 envelope。应用用 envelope 带内容、路由和会话标识。各个 hooks 共用当前 turn 的 `TurnState` 字典。 +这三个东西分工明确: + +- **envelope** 负责"从哪来、到哪去",跨进程边界。 +- **`TurnState`** 负责"这一轮记得什么",活在单次 turn 内。 +- **tape** 负责"之前发生过什么",跨 turn 长期存在。 + +要加一个新的消息来源,实现渠道,产出 envelope。要加一个在特定阶段生效的行为,实现插件 hook,读 `TurnState`。要留一份长期记录,写进 tape。 + [Turn](/zh-cn/docs/concepts/turn/)先把状态和提示词准备好,再调 agent。直接用 SDK 的人,自己把提示词和 state 交给 Agent。 -先按要用的能力选依赖,再按宿主选调用入口。一个 Python 服务可以复用应用插件,同时保留自己的请求处理和流。[复用插件](/zh-cn/docs/sdk/reuse-plugins/)讲怎么直接用组件,也讲 framework hooks 在嵌入的宿主里怎么接。 +## 别让两个东西干同一件事 {#one-owner} + +这是最常见的扩展设计错误,代价是以后没人说得清哪一处是权威。 + +- 已经在 `before_tool_call` 里拦了退款,就不要再在工具函数里重复判断。 +- 已经用技能写了提交规范,就不要再塞进 `AGENTS.md`。 +- 已经用 `provide_tape_context` 选了上下文,就不要再在 `build_prompt` 里拼一遍历史。 + +判断标准很简单:**这段逻辑如果被删掉,会不会有别的路径补不上?** 如果两条路径都能补上,就删掉一条。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/index.mdx b/website/src/content/docs/zh-cn/docs/concepts/index.mdx index c09453e54..8a5aff7f7 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/index.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/index.mdx @@ -1,45 +1,97 @@ --- title: 概览 -description: 两种用法共用的四个概念,各自在哪一层,以及去哪一页读。 +description: Bub 为什么拆成内核与插件,以及四个概念在一次交互里各管什么。 --- -Bub 有四个概念。**turn** 把一条消息送过整个应用。**tape** 存对话。**事件**报告循环做了什么。**扩展点**是新行为该放的地方。你自己的 Agent 和 Bub 应用都用这四个。 +import { Aside } from '@astrojs/starlight/components'; -顺序随意。每一页先给结论,再展开。 +## 背景 {#background} -## 分层 +Bub 的灵感源自于 OpenClaw、nanobot 那样的自我进化智能体,所以 Bub 最初把一切都堆在本体里: -`core.Agent` 接收模型、工具、提示词片段和一个 tape 存储。这些都由你传进来。一个小的 Python 服务,只装内核的依赖就能跑。 +- 每加一个消息渠道,就要在 `bub.channels` 里加一个类,外加一批工具或技能; +- 每加一项能力,就要多一个开关。 +- …… -`BubAgent` 加上应用的默认行为:配置、技能、命令、工具选择和 steering。`BubFramework` 在外围管理插件、消息处理和出站路由。需要这些行为时,再引入它们。 +即便用户只用到其中一两个渠道、一两个能力,却要面对几十个开关、配置项,直接被淹没在茫茫的选项里不得要领。 -`pip install bub` 只装内核。`pip install "bub[app]"` 两层都装。[安装指南](/zh-cn/docs/getting-started/install/)说明依赖怎么选。 +于是 Bub 被重写了——额外的功能被剥离,留下的只有精心设计的轻内核以及自由组合的插件架构。 -## 概念 +伴随着大模型的发展和进化,时至今日 Agent 早已不再是早期只会一问一答的聊天机器人,而是完全转变为和人一样对等的操作者、执行者,所以和其他 Agent 工具一样,Bub 诞生起就同样面临着如何与人协作、如何交接、如何执行时保持可见等社会化问题。 -| 概念 | 是什么 | 读哪页 | -| --- | --- | --- | -| Turn | 一条进来的消息,以及它引起的全部工作。 | [Turn](/zh-cn/docs/concepts/turn/) | -| Tape | 存下来的对话,以及模型实际读到的那一段。 | [Tape](/zh-cn/docs/concepts/tape/) | -| 事件 | 报告每一步的总线,处理器可以借它改掉下一步。 | [事件](/zh-cn/docs/concepts/events/) | -| 扩展点 | 新行为放的地方:工具、技能、插件或渠道。 | [扩展点](/zh-cn/docs/concepts/extension-points/) | +在精心设计架构基础上它应该要像艾萨克·阿西莫夫的机器人三定律一样有着明确的契约边界:明确的命令界限、可追溯的历史、运行保持中立, 最终实现从「助手行为」向「协作基础设施」的演进。 + +## 一次交互的旅程 {#journey} + +看一次最常见的交互:你在终端里问了一句,Bub 回了一段。 + +```mermaid +flowchart LR + A["消息"] --> B["准备"] --> C["循环"] --> D["收尾"] --> E["回复"] + C -.-> T[("tape")] +``` + +**准备**负责确定这次消息属于哪个会话、把上一轮留下的东西装回来、把消息转成提示词。**循环**拿着提示词去问模型,模型要求用工具就去执行,结果回到上下文,再问一次,直到模型直接给出答案。**收尾**把结果存下来,包成一条出站消息,交给渠道发出去。 + +三个环节里,只有中间那个是通用的:不管消息来自终端、Telegram 还是别的什么地方,"模型 ⇄ 工具"这一段都一模一样。前后的两个环节都在处理"消息从哪来、回哪去",而这恰恰是随部署变化的部分。 + +边界就画在这里。循环留在内核,两头交给应用。 + +## 四个概念 {#concepts} + +沿着这条线,四个概念依次登场。它们的顺序不是随意的:每一个都解决上一个留下的问题。 + +### Turn:把两头的杂事收进有名字的阶段 {#turn} + +既然两头归应用,那么"从收到消息到发出回复"这一整段就该有明确的形状。这就是 **turn**:一条进来的消息,以及它引起的全部工作。`BubFramework` 在它上面切出七个阶段,每个阶段都有名字,也都有对应的 hook。 + +这条设计沿用的是作者重写时定下的三条原则之一——单向依赖。应用层知道内核,内核不知道应用。内核不读配置、不认识插件;hooks 属于应用,由插件实现、由 `BubFramework` 调用。所以同一个 `core.Agent` 在两层里行为完全一致,你在 SDK 里验证过的东西,搬进 App 不会变。 + +**turn 里的三个词要分清**:一个 turn 可以包含一次**运行**(run,即一次 `agent.run()` 调用),一次运行可以包含多个**步骤**(step,即一次模型调用以及它要求的工具)。turn ⊃ run ⊃ step。 -## 术语 +想改消息怎么进来、怎么出去,就改这里。[读 Turn](/zh-cn/docs/concepts/turn/) -**Agent** 跑模型与工具的循环。一次**运行**(run)就是一次 `agent.run()` 调用,里面可以有好几个**步骤**(step)。一个**步骤**是一次模型调用,以及它要求的工具。一个 **turn** 是一条进来的消息,和回答它的那次运行。 +### Tape:把历史与上下文拆成两件事 {#tape} -**tape** 存对话。它的**条目**是消息、工具调用、结果和事件。**anchor** 标出上下文的起点。**handoff** 会写一个新的 anchor。turn 的 **state** 是一个独立字典,工具和处理器共用它。 +循环要读上下文,于是第一个问题是:上下文从哪来。 -应用另外加三样东西。**插件**提供 Python hooks。**技能** 写指令。**渠道**收发消息。 +最直觉的做法是维护一个 message 数组,每次对话往里追加。Bub 没有这么做,这是第二个设计原则——**追加式上下文**:每条记录都写进 **tape**,不改已有条目;模型读的是 tape 的一个切片,而不是一个不断变长的数组。 -## 设计说明 +拆开的好处很直接。历史里什么都在,事后可以查;上下文只挑该读的那几条,请求可以一直很短。也因为是只追加,中途反悔不会污染已经记下的东西。 -内核不读配置,也不读插件的 hooks。hooks 属于应用:插件实现它们,`BubFramework` 调用它们。所以内核在两层里的行为一样。 +代价是你要理解几个新词。tape 的**条目**(entry)是消息、工具调用、工具结果、事件和 anchor。**anchor** 标出上下文的起点,**handoff** 写一个新的 anchor 而**不删除**旧条目——它是遗忘的开关,不是压缩的开关。turn 的 **state** 则是另一个东西:一个本轮共享的普通字典,不落盘。 -历史与上下文分开。tape 留下每一条。上下文挑出下一次模型调用要读的条目。anchor 开一个新的上下文,前面的条目照旧保留。因此你可以查完整历史,同时只发一个短请求。 +想让 agent 记得更多或更少,就改这里。[读 Tape](/zh-cn/docs/concepts/tape/) -每个部分只干一件事。工具做动作。技能写做法。渠道把应用接到人干活的地方。职责分开,你才知道该改哪一块。 +### 事件:让循环把每一步说出来 {#events} -## 背景 +循环跑起来之后,宿主只剩一个问题:我怎么知道它在干什么? + +Bub 的答案是第三个设计原则——**事件驱动**。模型的每次调用、每个工具的每次执行,都发出开始、结束和结果。宿主可以旁观一次运行,也可以在调用真正发生之前改写它、甚至叫停它。 + +这里最容易混淆的是三种"事件":走 `EventBus` 的 `AgentEvent`(能拦、能改)、给调用方的 `StreamEvent`(只能读,改不了运行)、以及落到 tape 上的 `TapeEntry`(事后可查,不影响当前这一步)。方向搞错,行为就不对。 + +日志、权限、预算、审计、进度条,全都只是接在总线上的处理器,不需要动循环和工具。[读事件](/zh-cn/docs/concepts/events/) + +### 扩展点:动作、做法、连接,各归各位 {#extension-points} + +最后回到最初的问题:想加东西,加在哪? + +这里对应的是剩下两条原则。**工具即函数**——一个有类型标注的 Python 函数就是工具,所以"加一个动作"的成本极低。**插件化**——能力都可以由插件提供,插件可以是一个工具、一个事件监听器、一个消息渠道,或一整套技能。 + +但正因为它什么都能装,才更需要分清职责:工具做动作,**技能**(skill)写做法(只写指令,不执行代码),**渠道**(channel)接入人的工作环境,**插件**(plugin)把前几样打包装起来。渠道之间传递的通信单元叫 **envelope**;在一轮运行进行中插话叫 **steering**。 + +职责不重叠,你才知道该改哪一块。反过来说,Bub 没有"什么都不用改"的万能扩展点——先想清楚你要加的是动作、知识,还是连接。[读扩展点](/zh-cn/docs/concepts/extension-points/) + +## 小结 {#summary} + +四个概念对应的就是[核心设计理念](/zh-cn/docs/getting-started/#core-design)里的那些原则,只是换了个讲法: + +| 概念 | 它回答的问题 | 背后的原则 | +| --- | --- | --- | +| Turn | 一条消息进来后,应用依次做了什么? | 单向依赖:应用知道内核,内核不知道应用 | +| Tape | 历史存在哪,模型读到哪一段? | 追加式上下文:只写不改,读的是切片 | +| 事件 | 我怎么旁观、改写或叫停? | 事件驱动:调用开始、结束、结果都发出来 | +| 扩展点 | 我要加的东西该做成什么? | 工具即函数、插件化 | -[为什么重写 Bub](/zh-cn/posts/why-rewrite-bub/)和[社会化评估与智能体伙伴关系](/zh-cn/posts/socialized-evaluation/)讲了早期设计的来由。当前的接口和行为,以现在的指南和参考为准。 +顺序随意,但建议先读这一页:后面四页都在展开同一条旅程的不同切面。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/tape.mdx b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx index 4bb036891..9c1bda7fb 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/tape.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/tape.mdx @@ -3,11 +3,42 @@ title: Tape description: 对话存在哪里,下一次调用模型读到什么,以及一次运行结束后还能剩下什么。 --- -Agent 需要两样东西。一是自己干过什么的记录,二是下一次模型调用要用的一组消息。Bub 把记录存进 tape,再用你选中的条目拼出模型上下文。两者分开,你就能留下完整历史,同时只发一个短请求。 +import { Aside } from '@astrojs/starlight/components'; + +Agent 需要两样东西。一是自己干过什么的记录,二是下一次模型调用要用的一组消息。Bub 把记录存进 tape,再用你选中的条目拼出模型上下文。 + +两者分开,是 Bub 里最有用的一个决定:你可以留下完整历史,同时只发一个短请求。这一页讲它们各自怎么工作,以及什么时候会碰到边界。 `core.Agent` 和 `BubAgent` 共用这套机制。两者的默认存储和会话名不一样。 -## 存储 +## 一篇对话,两种读法 {#history-and-context} + +先把这个区分立起来,后面的内容都挂在它上面: + +```mermaid +flowchart LR + subgraph store["存储"] + E1["anchor
session/start"] + E2["message
user: 订单 A123 在哪"] + E3["tool_call
lookup_order"] + E4["tool_result"] + E5["message
assistant: 已发货"] + E6["event
loop.step"] + E7["anchor
order-reviewed"] + E8["message
user: 那运费呢"] + E1 --- E2 --- E3 --- E4 --- E5 --- E6 --- E7 --- E8 + end + + E1 -.->|"留下"| A["完整历史
查得到每一条"] + E7 ==>|"进入上下文"| B["模型读到
只有 anchor 之后这一段"] +``` + +- **历史**是全部条目,按写入顺序排列,只追加。事故复盘、审计、调试都读它。 +- **上下文**是筛出来的一个切片,转成 Republic 消息后交给模型。模型看不见被筛掉的部分。 + +筛选规则由 `TapeContext` 决定,默认规则是"最近一个 anchor 之后的会话条目"。 + +## 存储 {#storage} 普通的 `Agent` 默认创建 `InMemoryTapeStore`。历史跟着这个存储一起活,存储没了就没了。要在进程之间保留,传一个文件存储: @@ -20,7 +51,7 @@ from bub.core.store import FileTapeStore agent = Agent(store=FileTapeStore(Path("sessions"))) ``` -每次 `agent.run(..., tape="customer-42")` 用的都是这个存储里名为 `customer-42` 的 tape。名字相同就接着上一次聊,名字不同就分开。`Agent` 原样使用你给的名字。应用有自己的工作区与会话命名规则。 +每次 `agent.run(..., tape="customer-42")` 用的都是这个存储里名为 `customer-42` 的 tape。名字相同就接着上一次聊,名字不同就分开。`Agent` 原样使用你给的名字;应用有自己的工作区与会话命名规则,见[应用存储](#app-storage)。 `agent.tape(name)` 让你在一次运行之外看这个 tape。它的写入直接进存储: @@ -32,20 +63,13 @@ async def inspect_history() -> None: print(len(messages), "model messages;", len(entries), "stored entries") ``` -要自己写存储,实现 `bub.core.store` 里的 `TapeStore` 或 `AsyncTapeStore`。把实例传给 `Agent(store=my_store)` 就行。 - -| 方法 | 做什么 | -| --- | --- | -| `list_tapes()` | 返回已保存的 tape 名称。 | -| `fetch_all(query)` | 按 `TapeQuery` 的过滤和排序返回条目。 | -| `append(tape, entry)` | 往指定的 tape 追加一条。 | -| `reset(tape)` | 清空指定的 tape。 | +`read_messages()` 走上下文规则,`query()` 读原始条目——同一份 tape,两种读法,正是前面那张图。 -`TapeStore` 用同步方法,`AsyncTapeStore` 用异步方法。Agent 会把同步存储适配成异步调用。持久化和跨进程协调,由你的存储和应用负责。 +要自己写存储,实现 `bub.core.store` 里的 `TapeStore` 或 `AsyncTapeStore`。存储的读写接口、`TapeQuery` 的过滤条件,见 [Python API 参考](/zh-cn/docs/reference/python-api/)。持久化和跨进程协调由你的存储和应用负责;[SQLAlchemy 指南](/zh-cn/docs/integrations/storage/)给了一个完整的替换实现。 -## 上下文 +## 上下文 {#context} -tape 的条目有消息、工具调用、工具结果、事件和 anchor。正常记录只追加。要清空历史,得显式调用 `Tape.reset()` 这类操作。 +tape 的条目有消息、工具调用、工具结果、事件和 anchor。正常记录只追加;要清空历史,得显式调用 `Tape.reset()` 这类操作。 默认的 `TapeContext` 选出最近一个 anchor 之后的会话条目,再转成 Republic 消息: @@ -57,6 +81,10 @@ tape 的条目有消息、工具调用、工具结果、事件和 anchor。正 | `tool_result` | 工具消息,与前面的调用配对。 | | 其他事件 | 默认选择器跳过。 | +注意最后一行。**事件默认不进上下文**,所以你在 tape 上记什么和模型看到什么是两件事——这既是自由度也是陷阱:记了大量有用信息,模型却一条都看不到。 + +### 用 anchor 切上下文 {#anchors} + anchor 标出上下文的新起点。`handoff()` 写一个 anchor,前面的条目照旧保留: ```python @@ -67,38 +95,94 @@ async def start_next_phase() -> None: ) ``` -这里的 `state` 存在 anchor 上,位置在默认查询的起点之前。要把摘要带进下一阶段,就把它放进下一次提示词,或者在你的上下文策略里显式选它。要把值装回本轮的可变状态,得自己写加载逻辑。新会话会自动得到一个 `session/start` anchor。 +这里的 `state` 存在 anchor 上,位置在默认查询的起点之前。**要把摘要带进下一阶段,必须把它放进下一次提示词,或者在你的上下文策略里显式选它**——handoff 本身不会把摘要"传"给模型。 + +这就是 handoff 最常被误解的地方:它是**遗忘**的机制,不是记忆的机制。它决定旧的哪些看不见,不负责把旧的压缩成新的。 + +新会话会自动得到一个 `session/start` anchor,所以第一次运行也有起点。 + +### 换一套选择规则 {#custom-selectors} + +想换一个 anchor,就给 `Agent` 传 `context=TapeContext(...)`。也可以用自己的 `select(entries, context)` 函数: + +```python +from bub.core import Agent +from bub.core.context import default_tape_context +from bub.core.tape import TapeContext + + +def select_recent_business_events(entries, context): + """把业务事件也交给模型,而不只是消息。""" + return default_tape_context().select(entries, context) + + +agent = Agent(context=TapeContext(anchor=None, select=select_recent_business_events)) +``` + +选择器返回 Republic 消息,或者返回它们的 awaitable。`default_tape_context()` 含工具往返;不带 `select` 的 `TapeContext` 用更简单的消息转换。带 `context=False` 的条目,在默认或自定义选择器运行之前就被剔除。 + +### 上下文超长时 {#context-overflow} -想换一个 anchor,就给 `Agent` 传 `context=TapeContext(...)`。也可以用自己的 `select(entries, context)` 函数。选择器返回 Republic 消息,或者返回它们的 awaitable。`default_tape_context()` 含工具往返;不带 `select` 的 `TapeContext` 用更简单的消息转换。带 `context=False` 的条目,在默认或自定义选择器运行之前就被剔除。 +Agent 认出上下文长度错误时,会自动 handoff 一次,然后用更短的上下文重试。 -Agent 认出上下文长度错误时,会自动 handoff 一次,然后用更短的上下文重试。要保留前面历史的摘要,就得在 handoff 之前生成。如果剩下的请求还是太大,重试仍会失败。 + -## 状态 +需要按预算主动管理上下文时,[`bub-context-budget` 插件](/zh-cn/docs/sdk/reuse-plugins/)是一个可以直接用的例子:它在上限接近时提示模型调用 `tape.handoff`。 -传给 `agent.run()` 的 `state` 是一个可变字典。本轮的工具、提示词片段和事件处理器共用它。持久化由你的应用负责。长期数据放进自己的存储,或者把选定的事实写进 tape,需要时再读回来。 +## 状态 {#state} -默认的 [TapeRecorder](/zh-cn/docs/concepts/events/#recording)保存会话往返和一部分生命周期记录。其他变量、自定义事件和决策,要自己记录。 +传给 `agent.run()` 的 `state` 是一个可变字典。本轮的工具、提示词片段和事件处理器共用它。 -## 分支 +它**不是**持久化的东西。运行结束,这个字典就没有归属了。持久化由你的应用负责:长期数据放进自己的存储,或者把选定的事实写进 tape,需要时再读回来。 -每次 `agent.run()` 都会给它的 tape 开一个分支。流关闭时,分支把新条目追加到父 tape。抛异常或提前取消也一样。所以一次失败的运行,也可能留下有用的部分历史。 +默认的 [TapeRecorder](/zh-cn/docs/concepts/events/#recording) 保存会话往返和一部分生命周期记录。其他变量、自定义事件和决策,要自己记录——可以写进 `state` 顺带处理,也可以 `append_event` 写进 tape 长期保留。 + +| 要保存的东西 | 放哪 | +| --- | --- | +| 本轮临时计算的结果 | `state`,本轮结束即弃 | +| 需要事后查询的业务事实 | tape 的 `event` 条目 | +| 需要在下一轮读回的结构化数据 | 你自己的存储,或 anchor 的 `state` + 显式加载 | +| 要有完整审计的对话 | tape 本身(TapeRecorder 已经在做) | -传 `merge_back=False`,退出时丢掉分支的记录。这只影响 tape 写入。工具已经产生的副作用,比如发出的请求、改过的文件,都还在。 +## 分支 {#forks} + +每次 `agent.run()` 都会给它的 tape 开一个分支。流关闭时,分支把新条目追加到父 tape。 + +这个行为在异常路径上也成立:抛异常或提前取消,同样会合并。所以一次失败的运行,也可能留下有用的部分历史——调试时这点很有价值。 + +```mermaid +flowchart LR + P[("父 tape
已有条目")] + P --> F["fork
运行时写入这里"] + F -->|"流关闭 / 抛异常 / 取消"| M["合并回父 tape"] + F -.->|"merge_back=False"| D["丢弃本次写入"] + M --> P2[("父 tape
多了新条目")] +``` + +传 `merge_back=False`,退出时丢掉分支的记录。这只影响 tape 写入:工具已经产生的副作用,比如发出的请求、改过的文件,都还在。**分支是记录层的隔离,不是副作用层的事务。** 自己管一段工作时,用 `agent.session(name, merge_back=...)` 或 `tape.fork_tape(merge_back=...)`。两者默认都在退出时合并,异常退出也合并。 -两个运行共用一个 tape,又要看到对方做完的结果,就把它们串起来。会话锁由你的应用提供。[FastAPI 示例](/zh-cn/docs/sdk/web-service/)演示了单进程内的加锁。多个 worker 之间要另外协调。 +两个运行共用一个 tape,又要看到对方做完的结果,就把它们串起来。会话锁由你的应用提供。[FastAPI 示例](/zh-cn/docs/sdk/web-service/)演示了单进程内的加锁;多个 worker 之间要另外协调。 + +## Sidecar {#sidecars} + +`TapeSidecar` 把相关的数据存在另一个 tape 里,语义上仍属于同一个 tape。 + +挂载的 sidecar 跟着主 tape 一起分支、合并和重置。它的条目不进主上下文,所以不会挤占模型请求。你的应用需要时,用 `Agent(sidecars=...)` 传进去。 -## Sidecar +Bub 应用给大型工具结果挂了一个 spill sidecar。会话里留一小段预览和一个句柄,agent 可以用 `spill.read` 分段读。这就是"历史可以很大,上下文必须很小"的另一种落地方式:把体积移到旁边,而不是删掉。 -`TapeSidecar` 把相关的数据存在另一个 tape 里。挂载的 sidecar 跟着主 tape 一起分支、合并和重置。它的条目不进主上下文。你的应用需要时,用 `Agent(sidecars=...)` 传进去。 +合并分支或重置 tape 时,sidecar 的失败单独记录,主 tape 的操作照样完成——sidecar 是附加能力,坏了不该拖垮主流程。spill 的上限在[应用配置](/zh-cn/docs/app/configure/)里。 -Bub 应用给大型工具结果挂了一个 spill sidecar。会话里留一小段预览和一个句柄,agent 可以用 `spill.read` 分段读。合并分支或重置 tape 时,sidecar 的失败单独记录,主 tape 的操作照样完成。spill 的上限在[应用配置](/zh-cn/docs/app/configure/)里。 +## 应用存储 {#app-storage} -## 应用存储 +`BubAgent` 用的是 `BubFramework` 给的存储。内置应用用 Bub home 目录下的 `FuzzyFileTapeStore`,通常是 `~/.bub/tapes/`。 -`BubAgent` 用的是 `BubFramework` 给的存储。内置应用用 Bub home 目录下的 `FuzzyFileTapeStore`,通常是 `~/.bub/tapes/`。tape 名称由工作区路径和会话 ID 的哈希拼成。所以同一个会话 ID 在两个工作区里,历史是分开的。 +tape 名称由工作区路径和会话 ID 的哈希拼成。所以同一个会话 ID 在两个工作区里,历史是分开的——在 `~/work` 和 `~/scratch` 里用同一个会话名,看不到对方说过什么。 -要换存储,应用用 `provide_tape_store`。要选上下文,用 `build_tape_context`。[SQLAlchemy 指南](/zh-cn/docs/integrations/storage/)演示了这条插件路线。直接用 SDK 的服务,按前面说的把实现传给 `Agent(store=...)`。 +要换存储,应用用 `provide_tape_store`。要选上下文,用 `build_tape_context`。[SQLAlchemy 指南](/zh-cn/docs/integrations/storage/)演示了这条插件路线。直接用 SDK 的服务,按[存储](#storage)一节的说明把实现传给 `Agent(store=...)`。 内置应用会从 tape 记录里恢复部分会话设置。其他本轮状态要自己持久化。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx index 30167ea78..02b5af795 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx @@ -1,46 +1,81 @@ --- title: Turn -description: 一条进来的消息,从找会话到把回复发出去。 +description: 一条进来的消息,从找出会话到把回复发出去,以及每一步留给插件的位置。 --- -一个 turn 就是一条进来的消息,以及它引起的全部工作。`BubFramework` 负责一个 turn:找会话、装载状态、构建提示词、跑模型、发出回复。每个阶段都留了 hook,插件可以改这一段。 +import { Aside, Steps } from '@astrojs/starlight/components'; -turn 属于 `bub[app]`。直接调 `core.Agent.run()` 没有 turn:循环就用你给的提示词、state 和 tape 开始。 +一个 turn 就是一条进来的消息,以及它引起的全部工作。`BubFramework` 负责一个 turn:找会话、装载状态、构建提示词、跑模型、发出回复。每个阶段都留了 hook,插件可以补一段或整段替换。 -## 阶段 +turn 属于 `bub[app]`。直接调 `core.Agent.run()` 没有 turn:循环就用你给的提示词、state 和 tape 开始。这一页讲的是消息从渠道进来之后的那一层。 -`framework.process_inbound(message)` 按下面的顺序执行: +## 为什么需要 turn {#why-a-turn} -| 阶段 | 做什么 | -| --- | --- | -| `resolve_session` | 为收到的消息选出会话 ID。 | -| `load_state` | 从各个 hook 收集这个 turn 的状态。 | -| `build_prompt` | 把收到的内容转成提示词。 | -| `run_model` / `run_model_stream` | 产出模型输出:完整结果,或者一个流。 | -| `save_state` | 收尾模型阶段的工作。它在自己的 `finally` 块里运行。 | -| `render_outbound` | 把结果转成出站 envelope。 | -| `dispatch_outbound` | 发出每一个出站 envelope。 | +如果只有一个模型循环,你就得在每个调用点自己写一遍同样的事:这次消息属于哪个会话?上一次的状态要不要带上?回复应该发到哪里? -内置插件提供应用的默认行为。其他插件可以在 hook 允许的范围内补充或替换行为。[Hooks 参考](/zh-cn/docs/reference/hooks/)定义了首个有效结果、结果合并和优先级。 +turn 把这些从工具和模型代码里拿出来,固定成一串有名字的阶段。这样做的收益是具体的: -## Agent 调用 +- **每一段都可以替换**,但替换点有名字,不需要 fork 整个流程。 +- **错误发生在哪个阶段是明确的**,`on_error(stage=...)` 能拿到阶段名。 +- **一条消息是一个事务**,状态在开头装载、在结尾保存,中间的失败有确定的收尾行为。 -内置的 `run_model_stream` hook 调用 `BubAgent.run_stream()`。`BubAgent` 用的是内核的模型与工具循环,再加上配置、技能、命令、steering 和应用的工具策略。 +## 阶段 {#stages} -框架在流式与非流式 hook 结果之间做转换。`process_inbound(..., stream_output=True)` 让渠道路由器边收边处理,流结束时返回一个完整的 `TurnResult`。 +`framework.process_inbound(message)` 按顺序执行下面七个阶段。 -插件替换 `run_model` 就会改掉这一步。比如一个 echo 插件可以直接返回文本,根本不创建 agent run。只有选中的实现真的用了内核 agent,才会产生核心事件和 tape 记录。 + -## 资源和失败 +1. **`resolve_session`**:为收到的消息选出会话 ID。取首个非空结果;都为空时用渠道和聊天标识构造一个默认值。 -当 hooks 要提供存储或其他应用资源时,用 `async with framework.running():`。它打开这些资源,退出时释放。内置的单条消息 lifespan 在装载状态时打开,在 `save_state` 里关闭。 +2. **`load_state`**:从各个 hook 收集这个 turn 的状态片段,合并成一个 `TurnState` 字典。低优先级先合,高优先级覆盖。 -执行进到模型阶段后,`save_state` 就会运行,模型阶段抛异常也一样。会话解析、状态装载或 `build_prompt` 的失败发生在这个 `finally` 块之前,所以不会触发它。插件如果在这几个较早的阶段拿了资源,要自己释放。 +3. **`build_prompt`**:把收到的内容转成提示词。取首个结果;没有 hook 提供时,直接用入站内容。 -内置的 `save_state` 关闭单条消息 lifespan。应用另外保存选定的配置项。要保存其他状态,得自己定持久化策略。 +4. **`run_model` / `run_model_stream`**:产出模型输出,完整结果或一个流。内置实现调用 `BubAgent.run_stream()`,也就是进入下面那条循环。 -模型阶段抛出未处理异常时,框架先跑 `save_state`,然后跳过渲染和分发。外层的错误路径调用 `on_error(stage="turn", ...)`,再把异常抛出去。内置的错误观察器可以通过出站分发发一条错误回复。某个 `on_error` 观察器失败,不影响其他观察器。 +5. **`save_state`**:收尾模型阶段的工作。它在自己的 `finally` 块里运行,所以上一步抛异常也会走到这里。 + +6. **`render_outbound`**:把结果转成出站 envelope。多个 hook 的返回值拼在一起;全为空时用一条默认回复。 + +7. **`dispatch_outbound`**:把每一个出站 envelope 真正发出去,交给渠道。 + + + +前三个阶段准备输入,中间一个阶段产生输出,后三个阶段把输出送出去。只有第 4 步会跑很久——其余几步都是围绕它的前置和收尾。 + +内置插件提供这七个阶段的默认实现。其他插件可以在 hook 的约束内补充或替换行为。[Hooks 参考](/zh-cn/docs/reference/hooks/#turn-hooks)定义哪些 hook 取首个结果、哪些收集全部结果,以及优先级怎么算。 + +## 阶段之间传什么 {#what-crosses-stages} + +三个阶段之间只传三样东西,这是 turn 的完整内部状态: + +| 传递物 | 类型 | 谁写 | 谁读 | +| --- | --- | --- | --- | +| `session_id` | `str` | `resolve_session` | 后面所有阶段,以及 `TurnResult` | +| `state` | `TurnState` | `load_state` 的各个片段;运行期间工具和处理器也会改它 | 提示词构建、模型调用、工具、`save_state` | +| `prompt` | `list[UserContent]` | `build_prompt` | 模型阶段 | -## 下一步 +`state` 是最容易被忽略的一个。它是一个普通字典,本轮从装载到保存之间一直活着,工具和事件处理器拿到的是同一个对象。想让它跨 turn 存在,得自己在 `save_state` 里写出去,下次在 `load_state` 里读回来——内置应用只持久化选定的配置项。[Tape 的状态一节](/zh-cn/docs/concepts/tape/#state)讲了长期数据该放哪。 + +## 模型阶段 {#agent-call} + +内置的 `run_model_stream` hook 调用 `BubAgent.run_stream()`。`BubAgent` 用的是内核的模型与工具循环,再加上应用的默认行为:配置、技能、命令、steering 和工具策略。 + +框架会在流式与非流式 hook 结果之间做转换,所以插件只实现其中一种也能工作。`process_inbound(..., stream_output=True)` 让渠道路由器边收边处理,流结束时仍然返回一个完整的 `TurnResult`。 + +```python +result = await framework.process_inbound(message, stream_output=True) +print(result.session_id, len(result.outbounds)) +``` + +替换 `run_model` 就会改掉这一步。一个 echo 插件可以直接返回文本,根本不创建 agent run——这时不会有核心事件,也不会写入 tape 记录。**只有当被选中的实现真的用了核心 agent,事件和 tape 才会出现**,这是判断一次 turn 走了多远的最直接依据。 + +## 失败与资源 {#resources-and-failures} + + + +模型阶段抛出未处理异常时,框架先跑 `save_state`,然后跳过渲染和分发。外层的错误路径调用 `on_error(stage="turn", ...)`,再把异常抛出去。内置的错误观察器可以通过出站分发发一条错误回复。某个 `on_error` 观察器失败,不影响其他观察器。 -用[插件设置](/zh-cn/docs/app/plugins/)注册你的实现。常见改法见[Hooks](/zh-cn/docs/app/hooks/)。精确的签名和顺序见[Hooks 参考](/zh-cn/docs/reference/hooks/)。 +hooks 需要提供存储或其他应用资源时,用 `async with framework.running():`。它打开这些资源,退出时释放。内置的单条消息 lifespan 在装载状态时打开,在 `save_state` 里关闭。 diff --git a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx index 98f44625c..3a8048735 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/index.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/index.mdx @@ -32,7 +32,7 @@ Agent 的本质是一个循环:模型读取上下文并决定调用哪个工 二者并不互斥,必要时你甚至可以在 SDK 中[复用 App](/zh-cn/docs/sdk/reuse-plugins/#reuse-bubagent)。 -## 核心设计理念 +## 核心设计理念 {#core-design} Bub 秉持着简单、可组合、可扩展的设计理念,这意味着你可以自由组合 Bub 的能力,或在 Bub 的基础上构建自己的 Agent: diff --git a/website/src/integrations/pele.mjs b/website/src/integrations/pele.mjs index e199bb753..e1fdae15a 100644 --- a/website/src/integrations/pele.mjs +++ b/website/src/integrations/pele.mjs @@ -19,10 +19,17 @@ export const pelePlugin = { .update(`${context.fileURL}:${JSON.stringify(node.position)}:${node.value}`) .digest('hex').slice(0, 12); // Fail the build on invalid diagrams, and keep an SVG visible without JavaScript. + // + // Pele re-renders against the container width and flips `flowchart LR` to a + // vertical layout once the container is narrower than `directionBreakpoint` + // (640 by default). The docs column is 576px, so every left-to-right diagram + // silently became a tall one. Keep the declared direction and let the section + // below scroll horizontally instead. const { svg } = render(node.value, { idPrefix: `pele-${id}`, fontFamily: 'Outfit Variable, sans-serif', fontFamilyMono: 'JetBrains Mono Variable, monospace', + directionBreakpoint: 1, }); return { type: 'html', diff --git a/website/src/scripts/pele.ts b/website/src/scripts/pele.ts index 58d7aedd9..959c4faef 100644 --- a/website/src/scripts/pele.ts +++ b/website/src/scripts/pele.ts @@ -15,6 +15,10 @@ async function mountDiagrams() { try { const chinese = document.documentElement.lang.toLowerCase().startsWith('zh'); const diagram = await mountAsync(container, container.dataset.peleSource!, { + // The build keeps left-to-right diagrams horizontal; keeping the same + // breakpoint here stops the client re-render from flipping them back to + // a vertical layout in the narrow docs column. + directionBreakpoint: 1, zoom: { labels: chinese ? { zoomIn: '放大', zoomOut: '缩小', reset: '重置' } diff --git a/website/wrangler.jsonc b/website/wrangler.jsonc index 85d6f81f9..712b5cb95 100644 --- a/website/wrangler.jsonc +++ b/website/wrangler.jsonc @@ -3,7 +3,7 @@ "name": "bub", // Must not be newer than the `workerd` bundled with the pinned `wrangler`, or // `pnpm dev` / `pnpm preview` refuse to start. - "compatibility_date": "2026-10-13", + "compatibility_date": "2026-10-03", "observability": { "enabled": false, "head_sampling_rate": 1, From bff7071ead4289d9fbf602ff86fa93e205a4dbfb Mon Sep 17 00:00:00 2001 From: 100gle Date: Thu, 22 Oct 2026 18:14:14 +0800 Subject: [PATCH 18/28] docs(zh-cn): drop Next steps sections from the app guides --- website/src/content/docs/zh-cn/docs/app/channels.mdx | 6 ------ website/src/content/docs/zh-cn/docs/app/workspace.mdx | 7 ------- 2 files changed, 13 deletions(-) diff --git a/website/src/content/docs/zh-cn/docs/app/channels.mdx b/website/src/content/docs/zh-cn/docs/app/channels.mdx index 501e36c0c..6e68049db 100644 --- a/website/src/content/docs/zh-cn/docs/app/channels.mdx +++ b/website/src/content/docs/zh-cn/docs/app/channels.mdx @@ -149,9 +149,3 @@ telegram.start polling 普通消息进 turn 流水线之前会被设成 `output_channel="null"`。所以模型随口一句的回答不会被直接发回 Telegram。回复、进度更新、改消息,走的是内置的 `telegram` 技能。 逗号命令不一样。它们保留 `output_channel="telegram"`,工具输出会发回原来的 `chat_id`。内容是带 `message` 字段的 JSON 时,只发这个字段;否则发原始内容。空载荷直接丢掉。 - -## 下一步 - -- [终端](/zh-cn/docs/app/terminal/):REPL 和单次 turn。 -- [部署](/zh-cn/docs/app/deploy/):把机器人放进容器,状态持久化。 -- [设置参考](/zh-cn/docs/reference/settings/):全部渠道变量。 diff --git a/website/src/content/docs/zh-cn/docs/app/workspace.mdx b/website/src/content/docs/zh-cn/docs/app/workspace.mdx index 0deea7724..2dad20c44 100644 --- a/website/src/content/docs/zh-cn/docs/app/workspace.mdx +++ b/website/src/content/docs/zh-cn/docs/app/workspace.mdx @@ -37,10 +37,3 @@ bub run ",help" 输出会列出 `,skill`、`,tape.info` 这类命令。这一步只检查应用的命令路径,不调用模型。 命令以行首的逗号开头。未知命令会当成 shell 命令执行,所以检查时用上面这种已知命令。 - -## 下一步 - -- 增加[项目技能](/zh-cn/docs/app/skills/),让 Bub 按你的流程走。 -- 增加[工具](/zh-cn/docs/app/tools/),让 Bub 调你的代码。 -- 写[插件](/zh-cn/docs/app/plugins/),改应用的行为。 -- 在[终端](/zh-cn/docs/app/terminal/)里聊一次,或者[接一个渠道](/zh-cn/docs/app/channels/)。 From 9fe605b903fe71c2ead654fffcf29bd95b5caacf Mon Sep 17 00:00:00 2001 From: 100gle Date: Thu, 22 Oct 2026 18:14:14 +0800 Subject: [PATCH 19/28] fix(docs): restore the end-of-file newline in the install guide --- website/src/content/docs/zh-cn/docs/getting-started/install.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx index fb2407b21..9820dc10d 100644 --- a/website/src/content/docs/zh-cn/docs/getting-started/install.mdx +++ b/website/src/content/docs/zh-cn/docs/getting-started/install.mdx @@ -63,4 +63,4 @@ uv sync --- -一切就绪之后就可以开始上手试试怎么使用 Bub 吧! \ No newline at end of file +一切就绪之后就可以开始上手试试怎么使用 Bub 吧! From fb9f2acac445648e12f8e6ad03df7c4962fb513b Mon Sep 17 00:00:00 2001 From: 100gle Date: Thu, 22 Oct 2026 18:55:48 +0800 Subject: [PATCH 20/28] docs: drop the Next steps sections from the app and integration guides --- .../src/content/docs/docs/app/channels.mdx | 6 - .../src/content/docs/docs/app/configure.mdx | 6 - website/src/content/docs/docs/app/deploy.mdx | 6 - .../src/content/docs/docs/app/workspace.mdx | 7 -- .../src/content/docs/docs/concepts/index.mdx | 118 +++++++----------- .../src/content/docs/docs/concepts/turn.mdx | 11 ++ .../content/docs/docs/integrations/acp.mdx | 6 - .../docs/docs/integrations/agent-plugins.mdx | 6 - .../content/docs/docs/integrations/mcp.mdx | 6 - .../docs/docs/integrations/observability.mdx | 4 - .../docs/docs/integrations/storage.mdx | 6 - .../content/docs/zh-cn/docs/app/configure.mdx | 6 - .../content/docs/zh-cn/docs/app/deploy.mdx | 6 - .../docs/zh-cn/docs/concepts/index.mdx | 110 ++++++---------- .../content/docs/zh-cn/docs/concepts/turn.mdx | 11 ++ .../docs/zh-cn/docs/integrations/acp.mdx | 6 - .../zh-cn/docs/integrations/agent-plugins.mdx | 6 - .../docs/zh-cn/docs/integrations/mcp.mdx | 6 - .../zh-cn/docs/integrations/observability.mdx | 4 - .../docs/zh-cn/docs/integrations/storage.mdx | 6 - 20 files changed, 104 insertions(+), 239 deletions(-) diff --git a/website/src/content/docs/docs/app/channels.mdx b/website/src/content/docs/docs/app/channels.mdx index 06ffada8d..7b9b35247 100644 --- a/website/src/content/docs/docs/app/channels.mdx +++ b/website/src/content/docs/docs/app/channels.mdx @@ -149,9 +149,3 @@ The channel uses `telegram:` as the session id. A private chat and a gr A normal inbound message sets `output_channel="null"` before it enters the turn pipeline. A plain model answer is therefore not sent back blindly. The bundled `telegram` skill is the normal path for replies, progress updates, and edits. Comma commands are different. They keep `output_channel="telegram"`, so their tool output goes back to the originating `chat_id`. When the content is JSON with a `message` field, only that field is sent. Otherwise the raw content is sent. An empty payload is dropped. - -## Next steps - -- [Terminal](/docs/app/terminal/): the REPL and one-shot turns. -- [Deployment](/docs/app/deploy/): run the bot in a container with persistent state. -- [Settings reference](/docs/reference/settings/): every channel variable. diff --git a/website/src/content/docs/docs/app/configure.mdx b/website/src/content/docs/docs/app/configure.mdx index 7720c4ba1..781254d52 100644 --- a/website/src/content/docs/docs/app/configure.mdx +++ b/website/src/content/docs/docs/app/configure.mdx @@ -88,9 +88,3 @@ bub run "say hi" ``` The command prints a routing label (`[:]`) followed by the response. If you need to check whether a configuration plugin loaded, inspect it with `bub hooks`. - -## Next steps - -- [Channels](/docs/app/channels/): choose which surfaces Bub listens on. -- [Settings reference](/docs/reference/settings/): full env-var table. -- [Deploy](/docs/app/deploy/): package this config into a container. diff --git a/website/src/content/docs/docs/app/deploy.mdx b/website/src/content/docs/docs/app/deploy.mdx index b24890fbc..f55b8162b 100644 --- a/website/src/content/docs/docs/app/deploy.mdx +++ b/website/src/content/docs/docs/app/deploy.mdx @@ -134,9 +134,3 @@ Both commands operate on the managed project at `BUB_PROJECT` (default `bub.home :::note `bub install` and `bub update` require Bub itself to live in a virtual environment. The container image satisfies this; the `entrypoint.sh` activates `/app/.venv` before exec. ::: - -## Next steps - -- [Configure](/docs/app/configure/): env-var precedence and provider settings. -- [Channels](/docs/app/channels/#telegram): bot token and allowlist behavior. -- [CLI](/docs/reference/cli/): flags for `bub gateway`, `bub install`, `bub update`. diff --git a/website/src/content/docs/docs/app/workspace.mdx b/website/src/content/docs/docs/app/workspace.mdx index 73fa87460..3e8928cf8 100644 --- a/website/src/content/docs/docs/app/workspace.mdx +++ b/website/src/content/docs/docs/app/workspace.mdx @@ -37,10 +37,3 @@ bub run ",help" The output lists commands such as `,skill` and `,tape.info`. This checks the command path of the app without a model call. Commands begin with a comma at the start of a line. An unknown command falls back to a shell command, so use a known command for this check. - -## Next steps - -- Add a [project skill](/docs/app/skills/) for a procedure that you want Bub to follow. -- Add a [tool](/docs/app/tools/) when Bub must call your code. -- Write a [plugin](/docs/app/plugins/) to change application behavior. -- Run a conversation in the [terminal](/docs/app/terminal/), or [connect a channel](/docs/app/channels/). diff --git a/website/src/content/docs/docs/concepts/index.mdx b/website/src/content/docs/docs/concepts/index.mdx index fbe060ece..577711d8d 100644 --- a/website/src/content/docs/docs/concepts/index.mdx +++ b/website/src/content/docs/docs/concepts/index.mdx @@ -1,93 +1,63 @@ --- title: Overview -description: Why Bub splits into a kernel and plugins, and what the four concepts each own in one interaction. +description: Why Bub was redesigned and which responsibility boundaries it defines +prev: false +next: false --- +import { CardGrid, LinkCard } from '@astrojs/starlight/components'; + ## Background {#background} -Bub used to keep everything in the main package. Every new message channel meant another class under `bub.channels` plus a batch of tools or skills, and every new capability meant another switch. The author recorded the call at the time in [Why we rewrote Bub](/posts/why-rewrite-bub/): +Bub took its inspiration from self-evolving agents like OpenClaw and nanobot, so at first it piled every feature into the main package: -> If we kept going in that direction, Bub would eventually become another Nanobot, or even another OpenClaw. [...] Most users only need one or two providers and one or two channels, yet they still end up staring at a wall of settings and switches. Even if most of them are disabled by default, the complexity is still there, and users still get lost in it. +- Every new message channel required adding a class under `bub.channels`, plus a batch of tools or skills; +- Every new capability required one more switch. +- … -So the rewrite took the opposite direction: +As a result, even if a user only used one or two channels and one or two capabilities, they still faced dozens of switches and configuration options, and were simply drowned in a sea of options without knowing where to start. -> My answer is to split extra functionality out of the core and move to a model with a **carefully designed lightweight kernel** plus **freely vibe-coded feature plugins**. +So Bub was rewritten — stripping out extra functionality and leaving only a carefully designed lightweight kernel and a freely composable plugin architecture. -The kernel has to be stable, understandable by agents, and quality-controlled by maintainers. Plugins can be written however people like, even generated on the spot by an agent. One maintenance model is strict; the other is loose. +As large models have developed and evolved, agents today are no longer the early chatbots that only did question-and-answer. They have fully become operators and executors on equal footing with people. So, like other agent tools, Bub has faced social questions since its birth: how to collaborate with people, how to hand off work, and how to stay visible during execution. -Which raises the question: **once the pieces are split, where do the boundaries go?** This section is those boundaries. The four pages after it expand one each. +On the basis of a carefully designed architecture, it should have clear contractual boundaries like Isaac Asimov's Three Laws of Robotics: clear command boundaries, traceable history, and neutral operation, ultimately evolving from "assistant behavior" to "collaboration infrastructure." ## The journey of one interaction {#journey} -Take the most common interaction: you ask something in the terminal, and Bub answers. +Looking across all agent designs, the core interaction flow is as follows, and Bub is no exception: ```mermaid flowchart LR - A["message"] --> B["prepare"] --> C["loop"] --> D["wrap up"] --> E["reply"] - C -.-> T[("tape")] + A["Receive input"] --> B["Pre-processing"] --> C["Loop"] --> D["Post-processing"] --> E["Produce output"] + C -.-> T[("Conversation history")] ``` -**Prepare** decides which session the message belongs to, restores what the previous turn left behind, and turns the message into a prompt. **Loop** takes the prompt to the model, runs any tools the model asks for, feeds the results back into the context, and asks again until the model answers without asking for tools. **Wrap up** saves the result, packages it as an outbound message, and hands it to the channel. - -Of the three, only the middle one is general. It does not matter whether the message came from a terminal, Telegram, or anywhere else: the "model ⇄ tools" stretch is identical. The two stages around it both deal with where a message comes from and where the reply goes, and that is exactly the part that changes with deployment. - -That is where the line falls. The loop stays in the kernel; both ends belong to the app. - -## The four concepts {#concepts} - -Along that line, four concepts show up in order. The order is not arbitrary: each one answers a question the previous one left open. - -### Turn: gather both ends into named stages {#turn} - -If both ends belong to the app, then the whole stretch from receiving a message to sending a reply should have a clear shape. That is a **turn**: one inbound message, and all the work it causes. `BubFramework` cuts it into seven stages, each with a name and a matching hook. - -This follows one of the three principles the rewrite settled on: one-way dependencies. The app knows the kernel; the kernel does not know the app. The kernel reads no settings and recognizes no plugins. Hooks belong to the app: a plugin implements them, and `BubFramework` calls them. The same `core.Agent` therefore behaves identically in both layers, so what you verified through the SDK still holds inside the App. - -**Three words inside a turn need separating**: a turn can contain one **run** (one `agent.run()` call), and a run can contain several **steps** (one model call plus the tools it asks for). turn ⊃ run ⊃ step. - -To change how messages arrive or how replies leave, change this page. [Read Turn](/docs/concepts/turn/) - -### Tape: split history from context {#tape} - -The loop needs a context, so the first question is where that context comes from. - -The obvious approach is to keep a message array and append to it each round. Bub does not do that. This is the second principle, **append-only context**: every record is written to a **tape** without modifying earlier entries, and the model reads a slice of that tape rather than an ever-growing array. - -The benefit of splitting them is direct. History keeps everything, so you can audit it later; the context selects only the entries worth reading, so requests stay short. And because recording only appends, changing your mind never corrupts what is already stored. - -The cost is a few new words. A tape **entry** is a message, tool call, tool result, event, or anchor. An **anchor** marks where the context starts, and a **handoff** writes a new anchor **without deleting** older entries — it is a switch for forgetting, not a switch for compressing. The turn's **state** is something else again: a plain dictionary shared within the run, never persisted. - -To change how much the agent remembers, change this page. [Read Tape](/docs/concepts/tape/) - -### Events: let the loop narrate each step {#events} - -Once the loop is running, the host has one question left: how do I know what it is doing? - -Bub's answer is the third principle, **event-driven**. Every model call and every tool execution emits a start, an end, and a result. The host can watch a run, and can also rewrite a call before it happens, or stop it outright. - -The easiest thing to confuse here is the three kinds of "event": `AgentEvent` on the `EventBus` (you can intercept and change it), the `StreamEvent` given to the caller (read-only; it cannot change the run), and the `TapeEntry` written to the tape (readable afterwards; it does not affect the step in progress). Get the direction wrong and the behavior is wrong too. - -Logging, permissions, budgets, auditing, and progress bars are all just handlers attached to the bus. None of them requires touching the loop or the tools. [Read Events](/docs/concepts/events/) - -### Extension points: actions, procedures, connections {#extension-points} - -Last, back to the original question: you want to add something, so where does it go? - -This is where the remaining two principles land. **Tools are functions** — a type-hinted Python function is a tool, so adding an action is cheap. **Everything is a plugin** — capabilities can all be provided by plugins, and a plugin can be a tool, an event listener, a message channel, or a whole set of skills. - -But precisely because it can hold anything, the responsibilities need separating: a tool performs an action, a **skill** describes a procedure (instructions only, no code execution), a **channel** connects to where people work, and a **plugin** packages the earlier ones. The message unit passed between channels is an **envelope**; interjecting during a run is **steering**. - -Roles do not overlap, so you know which part to change. Conversely, Bub has no universal extension point that requires no other change — first decide whether you are adding an action, knowledge, or a connection. [Read Extension points](/docs/concepts/extension-points/) - -## Summary {#summary} - -The four concepts are the principles from [Core design](/docs/getting-started/#core-design), told a different way: - -| Concept | The question it answers | The principle behind it | -| --- | --- | --- | -| Turn | What does the app do, in order, after a message arrives? | One-way dependencies: the app knows the kernel, not the reverse | -| Tape | Where does history live, and what does the model read? | Append-only context: write, never modify; read a slice | -| Events | How do I watch, change, or stop a call? | Event-driven: start, end, and result are all emitted | -| Extension points | What should the thing I want to add become? | Tools are functions; everything is a plugin | - -Read the pages in any order, but start here: the other four expand different cuts of the same journey. +The whole flow still falls within software engineering. This means that apart from the two ends of receiving messages and producing output, every other process is an intermediate process, and most designs in the industry do their engineering in the intermediate process. + +## Responsibility boundaries {#concepts} + +Along this chain, Bub can be divided into the following responsibility boundaries. The boundaries have no dependency order; you can browse them as needed. + + + + + + + diff --git a/website/src/content/docs/docs/concepts/turn.mdx b/website/src/content/docs/docs/concepts/turn.mdx index 85630647b..243bca65b 100644 --- a/website/src/content/docs/docs/concepts/turn.mdx +++ b/website/src/content/docs/docs/concepts/turn.mdx @@ -9,6 +9,17 @@ A turn is one inbound message, and all the work that it causes. `BubFramework` h A turn belongs to `bub[app]`. A direct `core.Agent.run()` call has no turn — the loop starts with the prompt, state, and tape that you give it. This page covers the layer between a channel and that loop. +## Turn, run, and step {#turn-run-step} + +There are three granularities of "one" here. Separate them first: + +```mermaid +flowchart LR + T["Turn
all the work one message causes"] --> R["Run
one agent.run() call"] --> S["Step
one model call + the tools it requires"] +``` + +turn ⊃ run ⊃ step: a turn usually contains one run, and a run contains multiple steps. The reverse does not hold — when using the SDK directly, an `agent.run()` call can exist independently, with no turn; a step always belongs to a run. + ## Why a turn exists {#why-a-turn} With just a model loop, every call site has to answer the same questions again: which session is this message from? Should last round's state come along? Where does the reply go? diff --git a/website/src/content/docs/docs/integrations/acp.mdx b/website/src/content/docs/docs/integrations/acp.mdx index ff9b00324..590e51dbe 100644 --- a/website/src/content/docs/docs/integrations/acp.mdx +++ b/website/src/content/docs/docs/integrations/acp.mdx @@ -125,9 +125,3 @@ When the editor reopens an existing thread, `bub-acp-server` loads the matching | The editor cannot load or resume a thread | Confirm the editor is launching the same `bub` environment where `bub acp --help` works, and keep `BUB_HOME` stable across launches. | | Previous threads disappear after restarting the editor | Keep the same Bub environment and `BUB_HOME` across launches. `bub-acp-server` stores ACP session metadata under Bub home and loads history from Bub tapes. | | A turn starts but no activity is visible | Open Zed's `dev: open acp logs` command and inspect the JSON-RPC messages between Zed and Bub. | - -## Next steps - -- [Configure](/docs/app/configure/): Bub's model and runtime settings. -- [Turn](/docs/concepts/turn/): how Bub turns inbound messages into model calls and outbound messages. -- [Build plugins](/docs/app/plugins/): extend Bub with hooks, tools, and channels. diff --git a/website/src/content/docs/docs/integrations/agent-plugins.mdx b/website/src/content/docs/docs/integrations/agent-plugins.mdx index de17de947..7e70940ec 100644 --- a/website/src/content/docs/docs/integrations/agent-plugins.mdx +++ b/website/src/content/docs/docs/integrations/agent-plugins.mdx @@ -209,9 +209,3 @@ Download or update portable packages on disk, then restart Bub to load the chang | A skill is missing | Check that its frontmatter name matches the directory and that `skills_enabled` is true. Workspace or user skills can take precedence. | | An MCP tool is missing | Check the package's `mcp.json`, `mcp_enabled`, the enabled channels, and connection logs. Wait for bootstrap before calling the tool. | | Skills load but MCP does not | MCP validation and connection failures are isolated from skills. A malformed server entry is skipped without discarding valid sibling servers. | - -## Next steps - -- [Connect MCP servers](/docs/integrations/mcp/): manage servers in Bub's home configuration. -- [Build skills](/docs/app/skills/): write and validate skills for Bub. -- [Build plugins](/docs/app/plugins/): extend Bub's hooks with a Python package. diff --git a/website/src/content/docs/docs/integrations/mcp.mdx b/website/src/content/docs/docs/integrations/mcp.mdx index d07b184f6..126ead0b9 100644 --- a/website/src/content/docs/docs/integrations/mcp.mdx +++ b/website/src/content/docs/docs/integrations/mcp.mdx @@ -179,9 +179,3 @@ bub mcp remove time | `bub mcp add` prints `CancelledError` after `Added MCP server …` | The entry may already be written. Use `bub mcp list` to verify, or edit `mcp.json` by hand. | | Tool never called during a turn | Confirm `bub mcp list` shows `Status: Connected` and lists the expected tool, then ask a question that clearly maps to that tool. | | Permission denied on `mcp.json` | Verify `~/.bub/` is writable, or set `BUB_HOME` to a directory you own. | - -## Next steps - -- [Load Agent Plugins](/docs/integrations/agent-plugins/): load portable skills and MCP servers alongside `mcp.json`. -- [Configure](/docs/app/configure/): Bub's config layer and environment variables. -- [bub-mcp source](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-mcp): the plugin's full source and advanced options. diff --git a/website/src/content/docs/docs/integrations/observability.mdx b/website/src/content/docs/docs/integrations/observability.mdx index ab5842de6..757af0b0b 100644 --- a/website/src/content/docs/docs/integrations/observability.mdx +++ b/website/src/content/docs/docs/integrations/observability.mdx @@ -193,7 +193,3 @@ Stop Phoenix with `Ctrl+C` if it is running in the foreground. If it is detached ```bash docker rm -f bub-phoenix ``` - -## Next steps - -- [Tape and context](/docs/concepts/tape/): understand what Bub records and how context is rebuilt. diff --git a/website/src/content/docs/docs/integrations/storage.mdx b/website/src/content/docs/docs/integrations/storage.mdx index fbe0be69f..a44114ea6 100644 --- a/website/src/content/docs/docs/integrations/storage.mdx +++ b/website/src/content/docs/docs/integrations/storage.mdx @@ -105,9 +105,3 @@ For a SQLite-focused store with vector-search support, see [`bub-tapestore-sqlit unset BUB_TAPESTORE_SQLALCHEMY_URL BUB_TAPESTORE_SQLALCHEMY_CONNECT_ARGS rm -f "$PWD/bub-tapes.db" ``` - -## Next steps - -- [Observe Bub with tapes and Phoenix](/docs/integrations/observability/): pair the durable tape with GenAI telemetry. -- [Tape and context](/docs/concepts/tape/): what Bub records and how context is rebuilt. -- [Plugins](/docs/app/plugins/): the plugin contract `bub-tapestore-sqlalchemy` follows. diff --git a/website/src/content/docs/zh-cn/docs/app/configure.mdx b/website/src/content/docs/zh-cn/docs/app/configure.mdx index 3f35d9d1f..d01db4917 100644 --- a/website/src/content/docs/zh-cn/docs/app/configure.mdx +++ b/website/src/content/docs/zh-cn/docs/app/configure.mdx @@ -88,9 +88,3 @@ bub run "say hi" ``` 命令会先打印路由标记 `[:]`,再显示响应正文。需要确认配置插件是否加载时,可以使用 `bub hooks`。 - -## 后续阅读 - -- [渠道](/zh-cn/docs/app/channels/):选择 Bub 监听的接入面。 -- [设置参考](/zh-cn/docs/reference/settings/):完整环境变量表。 -- [部署](/zh-cn/docs/app/deploy/):把这份配置打入容器。 diff --git a/website/src/content/docs/zh-cn/docs/app/deploy.mdx b/website/src/content/docs/zh-cn/docs/app/deploy.mdx index e5a92e7c7..b202cbc8b 100644 --- a/website/src/content/docs/zh-cn/docs/app/deploy.mdx +++ b/website/src/content/docs/zh-cn/docs/app/deploy.mdx @@ -134,9 +134,3 @@ docker compose exec app bub update specific-plugin # 升级指定插件 :::note `bub install` 与 `bub update` 要求 Bub 自身位于虚拟环境中。容器镜像满足该条件,`entrypoint.sh` 会先 activate `/app/.venv` 再 exec。 ::: - -## 后续阅读 - -- [配置](/zh-cn/docs/app/configure/):环境变量优先级与提供商设置。 -- [渠道](/zh-cn/docs/app/channels/#telegram):bot token 与白名单行为。 -- [CLI](/zh-cn/docs/reference/cli/):`bub gateway`、`bub install`、`bub update` 的参数。 diff --git a/website/src/content/docs/zh-cn/docs/concepts/index.mdx b/website/src/content/docs/zh-cn/docs/concepts/index.mdx index 8a5aff7f7..8166ef52d 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/index.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/index.mdx @@ -1,21 +1,23 @@ --- title: 概览 -description: Bub 为什么拆成内核与插件,以及四个概念在一次交互里各管什么。 +description: 讲述 Bub 为什么被重新设计以及定义了哪些职责边界 +prev: false +next: false --- -import { Aside } from '@astrojs/starlight/components'; +import { CardGrid, LinkCard } from '@astrojs/starlight/components'; ## 背景 {#background} -Bub 的灵感源自于 OpenClaw、nanobot 那样的自我进化智能体,所以 Bub 最初把一切都堆在本体里: +Bub 的灵感源自于 OpenClaw、nanobot 那样的自我进化智能体,所以最初把一切功能都堆在本体里: - 每加一个消息渠道,就要在 `bub.channels` 里加一个类,外加一批工具或技能; - 每加一项能力,就要多一个开关。 - …… -即便用户只用到其中一两个渠道、一两个能力,却要面对几十个开关、配置项,直接被淹没在茫茫的选项里不得要领。 +这就导致即便用户只用到其中一两个渠道、一两个能力,也要面临几十个开关、配置项,直接被淹没在茫茫的选项里不得要领。 -于是 Bub 被重写了——额外的功能被剥离,留下的只有精心设计的轻内核以及自由组合的插件架构。 +于是 Bub 被重写了——剥离额外功能,只有留下精心设计的轻内核以及自由组合的插件架构。 伴随着大模型的发展和进化,时至今日 Agent 早已不再是早期只会一问一答的聊天机器人,而是完全转变为和人一样对等的操作者、执行者,所以和其他 Agent 工具一样,Bub 诞生起就同样面临着如何与人协作、如何交接、如何执行时保持可见等社会化问题。 @@ -23,75 +25,39 @@ Bub 的灵感源自于 OpenClaw、nanobot 那样的自我进化智能体,所 ## 一次交互的旅程 {#journey} -看一次最常见的交互:你在终端里问了一句,Bub 回了一段。 +纵观所有 Agent 设计,其核心交互流程都如下所示,Bub 也不例外: ```mermaid flowchart LR - A["消息"] --> B["准备"] --> C["循环"] --> D["收尾"] --> E["回复"] - C -.-> T[("tape")] + A["接收输入"] --> B["前置操作"] --> C["循环"] --> D["后置操作"] --> E["产生输出"] + C -.-> T[("会话历史")] ``` -**准备**负责确定这次消息属于哪个会话、把上一轮留下的东西装回来、把消息转成提示词。**循环**拿着提示词去问模型,模型要求用工具就去执行,结果回到上下文,再问一次,直到模型直接给出答案。**收尾**把结果存下来,包成一条出站消息,交给渠道发出去。 - -三个环节里,只有中间那个是通用的:不管消息来自终端、Telegram 还是别的什么地方,"模型 ⇄ 工具"这一段都一模一样。前后的两个环节都在处理"消息从哪来、回哪去",而这恰恰是随部署变化的部分。 - -边界就画在这里。循环留在内核,两头交给应用。 - -## 四个概念 {#concepts} - -沿着这条线,四个概念依次登场。它们的顺序不是随意的:每一个都解决上一个留下的问题。 - -### Turn:把两头的杂事收进有名字的阶段 {#turn} - -既然两头归应用,那么"从收到消息到发出回复"这一整段就该有明确的形状。这就是 **turn**:一条进来的消息,以及它引起的全部工作。`BubFramework` 在它上面切出七个阶段,每个阶段都有名字,也都有对应的 hook。 - -这条设计沿用的是作者重写时定下的三条原则之一——单向依赖。应用层知道内核,内核不知道应用。内核不读配置、不认识插件;hooks 属于应用,由插件实现、由 `BubFramework` 调用。所以同一个 `core.Agent` 在两层里行为完全一致,你在 SDK 里验证过的东西,搬进 App 不会变。 - -**turn 里的三个词要分清**:一个 turn 可以包含一次**运行**(run,即一次 `agent.run()` 调用),一次运行可以包含多个**步骤**(step,即一次模型调用以及它要求的工具)。turn ⊃ run ⊃ step。 - -想改消息怎么进来、怎么出去,就改这里。[读 Turn](/zh-cn/docs/concepts/turn/) - -### Tape:把历史与上下文拆成两件事 {#tape} - -循环要读上下文,于是第一个问题是:上下文从哪来。 - -最直觉的做法是维护一个 message 数组,每次对话往里追加。Bub 没有这么做,这是第二个设计原则——**追加式上下文**:每条记录都写进 **tape**,不改已有条目;模型读的是 tape 的一个切片,而不是一个不断变长的数组。 - -拆开的好处很直接。历史里什么都在,事后可以查;上下文只挑该读的那几条,请求可以一直很短。也因为是只追加,中途反悔不会污染已经记下的东西。 - -代价是你要理解几个新词。tape 的**条目**(entry)是消息、工具调用、工具结果、事件和 anchor。**anchor** 标出上下文的起点,**handoff** 写一个新的 anchor 而**不删除**旧条目——它是遗忘的开关,不是压缩的开关。turn 的 **state** 则是另一个东西:一个本轮共享的普通字典,不落盘。 - -想让 agent 记得更多或更少,就改这里。[读 Tape](/zh-cn/docs/concepts/tape/) - -### 事件:让循环把每一步说出来 {#events} - -循环跑起来之后,宿主只剩一个问题:我怎么知道它在干什么? - -Bub 的答案是第三个设计原则——**事件驱动**。模型的每次调用、每个工具的每次执行,都发出开始、结束和结果。宿主可以旁观一次运行,也可以在调用真正发生之前改写它、甚至叫停它。 - -这里最容易混淆的是三种"事件":走 `EventBus` 的 `AgentEvent`(能拦、能改)、给调用方的 `StreamEvent`(只能读,改不了运行)、以及落到 tape 上的 `TapeEntry`(事后可查,不影响当前这一步)。方向搞错,行为就不对。 - -日志、权限、预算、审计、进度条,全都只是接在总线上的处理器,不需要动循环和工具。[读事件](/zh-cn/docs/concepts/events/) - -### 扩展点:动作、做法、连接,各归各位 {#extension-points} - -最后回到最初的问题:想加东西,加在哪? - -这里对应的是剩下两条原则。**工具即函数**——一个有类型标注的 Python 函数就是工具,所以"加一个动作"的成本极低。**插件化**——能力都可以由插件提供,插件可以是一个工具、一个事件监听器、一个消息渠道,或一整套技能。 - -但正因为它什么都能装,才更需要分清职责:工具做动作,**技能**(skill)写做法(只写指令,不执行代码),**渠道**(channel)接入人的工作环境,**插件**(plugin)把前几样打包装起来。渠道之间传递的通信单元叫 **envelope**;在一轮运行进行中插话叫 **steering**。 - -职责不重叠,你才知道该改哪一块。反过来说,Bub 没有"什么都不用改"的万能扩展点——先想清楚你要加的是动作、知识,还是连接。[读扩展点](/zh-cn/docs/concepts/extension-points/) - -## 小结 {#summary} - -四个概念对应的就是[核心设计理念](/zh-cn/docs/getting-started/#core-design)里的那些原则,只是换了个讲法: - -| 概念 | 它回答的问题 | 背后的原则 | -| --- | --- | --- | -| Turn | 一条消息进来后,应用依次做了什么? | 单向依赖:应用知道内核,内核不知道应用 | -| Tape | 历史存在哪,模型读到哪一段? | 追加式上下文:只写不改,读的是切片 | -| 事件 | 我怎么旁观、改写或叫停? | 事件驱动:调用开始、结束、结果都发出来 | -| 扩展点 | 我要加的东西该做成什么? | 工具即函数、插件化 | - -顺序随意,但建议先读这一页:后面四页都在展开同一条旅程的不同切面。 +整个流程仍然属于软件工程范畴,这就意味着除了接收消息和产生输出两端之外,其他过程都属于中间过程,业界所有的设计大多数都在中间过程做工程化设计。 + +## 职责边界 {#concepts} + +沿这条链路,可以将 Bub 划分出如下职责边界,每块职责边界没有先后依赖顺序,你可以按需浏览。 + + + + + + + diff --git a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx index 02b5af795..ee1a8af5e 100644 --- a/website/src/content/docs/zh-cn/docs/concepts/turn.mdx +++ b/website/src/content/docs/zh-cn/docs/concepts/turn.mdx @@ -9,6 +9,17 @@ import { Aside, Steps } from '@astrojs/starlight/components'; turn 属于 `bub[app]`。直接调 `core.Agent.run()` 没有 turn:循环就用你给的提示词、state 和 tape 开始。这一页讲的是消息从渠道进来之后的那一层。 +## turn、run 与 step {#turn-run-step} + +同一个“一次”有三种粒度,先分清它们: + +```mermaid +flowchart LR + T["Turn
一条消息引起的全部工作"] --> R["Run
一次 agent.run()"] --> S["Step
一次模型调用 + 它要求的工具"] +``` + +turn ⊃ run ⊃ step:一次 turn 通常包含一次 run,一次 run 又包含多个 step。反过来不成立——直接用 SDK 时,一次 `agent.run()` 可以独立存在,没有 turn;而 step 始终属于某一次 run。 + ## 为什么需要 turn {#why-a-turn} 如果只有一个模型循环,你就得在每个调用点自己写一遍同样的事:这次消息属于哪个会话?上一次的状态要不要带上?回复应该发到哪里? diff --git a/website/src/content/docs/zh-cn/docs/integrations/acp.mdx b/website/src/content/docs/zh-cn/docs/integrations/acp.mdx index 744cea8ae..626e359e7 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/acp.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/acp.mdx @@ -125,9 +125,3 @@ HELLO | 编辑器无法加载或恢复会话 | 确认编辑器启动的是同一个 `bub` 环境,且其中 `bub acp --help` 可用;同时保持每次启动使用稳定的 `BUB_HOME`。 | | 重启编辑器后旧线程消失 | 保持每次启动使用同一个 Bub 环境和 `BUB_HOME`。`bub-acp-server` 会把 ACP 会话元数据保存在 Bub 数据目录下,并从 Bub Tape 加载历史。 | | turn 已开始但界面没有活动显示 | 在 Zed 中运行 `dev: open acp logs`,检查 Zed 与 Bub 之间的 JSON-RPC 消息。 | - -## 下一步 - -- [配置](/zh-cn/docs/app/configure/):Bub 的模型和运行时设置。 -- [Turn](/zh-cn/docs/concepts/turn/):Bub 如何把入站消息转成模型调用和出站消息。 -- [构建插件](/zh-cn/docs/app/plugins/):用 hooks、工具和渠道扩展 Bub。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/agent-plugins.mdx b/website/src/content/docs/zh-cn/docs/integrations/agent-plugins.mdx index dc63f2806..72b8511b6 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/agent-plugins.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/agent-plugins.mdx @@ -201,9 +201,3 @@ stdio 服务器会收到绝对路径形式的 `PLUGIN_ROOT` 和 `PLUGIN_DATA` | 技能未出现 | 检查 frontmatter 中的名称是否与目录一致,以及 `skills_enabled` 是否开启。工作区或用户目录中的同名技能可能优先。 | | MCP 工具未出现 | 检查插件包的 `mcp.json`、`mcp_enabled`、已启用的渠道和连接日志。等待 bootstrap 完成后再调用工具。 | | 技能可用但 MCP 不可用 | MCP 校验和连接失败不会影响技能。无效服务器条目会被跳过,同文件中的其他有效服务器仍可加载。 | - -## 下一步 - -- [接入 MCP 服务器](/zh-cn/docs/integrations/mcp/):管理 Bub 数据目录配置中的服务器。 -- [构建技能](/zh-cn/docs/app/skills/):编写并验证 Bub 技能。 -- [构建插件](/zh-cn/docs/app/plugins/):使用 Python 包扩展 Bub hooks。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx index 5262897d2..e6709c25d 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/mcp.mdx @@ -179,9 +179,3 @@ bub mcp remove time | `bub mcp add` 在 `Added MCP server …` 之后打印 `CancelledError` | 条目可能已经写入。用 `bub mcp list` 复核,或改为手动编辑 `mcp.json`。 | | turn 中工具未被调用 | 确认 `bub mcp list` 显示 `Status: Connected` 且列出了预期工具,然后提一个明显需要该工具的问题。 | | `mcp.json` 权限被拒绝 | 确认 `~/.bub/` 对当前用户可写,或将 `BUB_HOME` 设到你拥有的目录后重试。 | - -## 下一步 - -- [加载 Agent Plugins](/zh-cn/docs/integrations/agent-plugins/):在 `mcp.json` 之外加载可移植的技能和 MCP 服务器。 -- [配置](/zh-cn/docs/app/configure/):Bub 的配置层与环境变量。 -- [bub-mcp 源码](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-mcp):插件完整源码与高级选项。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/observability.mdx b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx index 7866567d7..ed11dd5ab 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/observability.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/observability.mdx @@ -193,7 +193,3 @@ Tape 投影方式的细节见 [contrib 插件实现](https://github.com/bubbuild ```bash docker rm -f bub-phoenix ``` - -## 下一步 - -- [Tape 与上下文](/zh-cn/docs/concepts/tape/):理解 Bub 记录什么,以及如何重建上下文。 diff --git a/website/src/content/docs/zh-cn/docs/integrations/storage.mdx b/website/src/content/docs/zh-cn/docs/integrations/storage.mdx index 92c0f65f6..72ae5b082 100644 --- a/website/src/content/docs/zh-cn/docs/integrations/storage.mdx +++ b/website/src/content/docs/zh-cn/docs/integrations/storage.mdx @@ -105,9 +105,3 @@ Bub 现在将同样的 Tape 条目写入 SQLite,替代应用默认的文件存 unset BUB_TAPESTORE_SQLALCHEMY_URL BUB_TAPESTORE_SQLALCHEMY_CONNECT_ARGS rm -f "$PWD/bub-tapes.db" ``` - -## 下一步 - -- [使用 Tape 与 Phoenix 观察 Bub](/zh-cn/docs/integrations/observability/):把持久化 Tape 和 GenAI 遥测配合使用。 -- [Tape 与上下文](/zh-cn/docs/concepts/tape/):理解 Bub 记录什么,以及如何重建上下文。 -- [插件](/zh-cn/docs/app/plugins/):`bub-tapestore-sqlalchemy` 遵循的插件契约。 From 686b78609b89230a39f3a55608a86eb83cbb6243 Mon Sep 17 00:00:00 2001 From: 100gle Date: Fri, 23 Oct 2026 13:31:36 +0800 Subject: [PATCH 21/28] chore: add badge for turn --- website/TRANSLATING.md | 7 +- website/astro.config.mjs | 17 +- .../src/content/docs/docs/app/channels.mdx | 151 ------ .../src/content/docs/docs/app/configure.mdx | 98 ++-- website/src/content/docs/docs/app/deploy.mdx | 168 +++++-- .../content/docs/docs/app/distribution.mdx | 88 +++- website/src/content/docs/docs/app/gateway.mdx | 288 ++++++++++++ website/src/content/docs/docs/app/hooks.mdx | 189 ++++++-- website/src/content/docs/docs/app/plugins.mdx | 150 +++--- website/src/content/docs/docs/app/skills.mdx | 100 ++-- .../src/content/docs/docs/app/subagents.mdx | 99 ++++ .../src/content/docs/docs/app/terminal.mdx | 111 ++++- website/src/content/docs/docs/app/tools.mdx | 103 +++-- .../src/content/docs/docs/app/workspace.mdx | 32 +- .../src/content/docs/docs/concepts/events.mdx | 189 +++----- .../docs/docs/concepts/extension-points.mdx | 121 +++-- .../src/content/docs/docs/concepts/tape.mdx | 218 ++++----- .../src/content/docs/docs/concepts/turn.mdx | 117 ++--- .../docs/docs/getting-started/index.mdx | 20 +- .../docs/docs/getting-started/install.mdx | 6 +- .../docs/docs/getting-started/quickstart.mdx | 8 +- .../content/docs/docs/integrations/acp.mdx | 149 +++--- .../docs/docs/integrations/agent-plugins.mdx | 241 +++++----- .../content/docs/docs/integrations/index.mdx | 24 - .../docs/docs/integrations/local-models.mdx | 127 ++--- .../content/docs/docs/integrations/mcp.mdx | 208 ++++----- .../docs/docs/integrations/observability.mdx | 162 +++---- .../docs/docs/integrations/storage.mdx | 107 +++-- .../src/content/docs/docs/reference/cli.mdx | 359 +++++++++------ .../src/content/docs/docs/reference/hooks.mdx | 249 +++++++--- .../docs/docs/reference/python-api.mdx | 316 +++++++++---- .../content/docs/docs/reference/settings.mdx | 433 +++++++++++++---- .../src/content/docs/docs/reference/tools.mdx | 238 ++++++++++ website/src/content/docs/docs/sdk/index.md | 51 +- website/src/content/docs/docs/sdk/options.mdx | 134 ++++-- .../content/docs/docs/sdk/reuse-plugins.mdx | 137 +++--- website/src/content/docs/docs/sdk/tools.mdx | 226 ++++++--- .../src/content/docs/docs/sdk/web-service.mdx | 18 +- .../content/docs/zh-cn/docs/app/channels.mdx | 151 ------ .../content/docs/zh-cn/docs/app/configure.mdx | 97 ++-- .../content/docs/zh-cn/docs/app/deploy.mdx | 168 +++++-- .../docs/zh-cn/docs/app/distribution.mdx | 88 +++- .../content/docs/zh-cn/docs/app/gateway.mdx | 288 ++++++++++++ .../src/content/docs/zh-cn/docs/app/hooks.mdx | 197 ++++++-- .../content/docs/zh-cn/docs/app/plugins.mdx | 148 +++--- .../content/docs/zh-cn/docs/app/skills.mdx | 98 ++-- .../content/docs/zh-cn/docs/app/subagents.mdx | 101 ++++ .../content/docs/zh-cn/docs/app/terminal.mdx | 111 ++++- .../src/content/docs/zh-cn/docs/app/tools.mdx | 99 ++-- .../content/docs/zh-cn/docs/app/workspace.mdx | 28 +- .../docs/zh-cn/docs/concepts/events.mdx | 189 +++----- .../zh-cn/docs/concepts/extension-points.mdx | 123 +++-- .../content/docs/zh-cn/docs/concepts/tape.mdx | 218 ++++----- .../content/docs/zh-cn/docs/concepts/turn.mdx | 117 ++--- .../docs/zh-cn/docs/getting-started/index.mdx | 2 +- .../zh-cn/docs/getting-started/install.mdx | 8 +- .../zh-cn/docs/getting-started/quickstart.mdx | 6 +- .../docs/zh-cn/docs/integrations/acp.mdx | 151 +++--- .../zh-cn/docs/integrations/agent-plugins.mdx | 229 ++++----- .../docs/zh-cn/docs/integrations/index.mdx | 24 - .../zh-cn/docs/integrations/local-models.mdx | 125 ++--- .../docs/zh-cn/docs/integrations/mcp.mdx | 212 ++++----- .../zh-cn/docs/integrations/observability.mdx | 154 ++----- .../docs/zh-cn/docs/integrations/storage.mdx | 107 +++-- .../content/docs/zh-cn/docs/reference/cli.mdx | 366 +++++++++------ .../docs/zh-cn/docs/reference/hooks.mdx | 251 +++++++--- .../docs/zh-cn/docs/reference/python-api.mdx | 316 +++++++++---- .../docs/zh-cn/docs/reference/settings.mdx | 434 +++++++++++++----- .../docs/zh-cn/docs/reference/tools.mdx | 238 ++++++++++ .../src/content/docs/zh-cn/docs/sdk/index.md | 51 +- .../content/docs/zh-cn/docs/sdk/options.mdx | 138 ++++-- .../docs/zh-cn/docs/sdk/reuse-plugins.mdx | 137 +++--- .../src/content/docs/zh-cn/docs/sdk/tools.mdx | 222 +++++++-- .../docs/zh-cn/docs/sdk/web-service.mdx | 16 +- website/src/styles/global.css | 65 +++ 75 files changed, 7017 insertions(+), 3915 deletions(-) delete mode 100644 website/src/content/docs/docs/app/channels.mdx create mode 100644 website/src/content/docs/docs/app/gateway.mdx create mode 100644 website/src/content/docs/docs/app/subagents.mdx delete mode 100644 website/src/content/docs/docs/integrations/index.mdx create mode 100644 website/src/content/docs/docs/reference/tools.mdx delete mode 100644 website/src/content/docs/zh-cn/docs/app/channels.mdx create mode 100644 website/src/content/docs/zh-cn/docs/app/gateway.mdx create mode 100644 website/src/content/docs/zh-cn/docs/app/subagents.mdx delete mode 100644 website/src/content/docs/zh-cn/docs/integrations/index.mdx create mode 100644 website/src/content/docs/zh-cn/docs/reference/tools.mdx diff --git a/website/TRANSLATING.md b/website/TRANSLATING.md index 785277475..a7a478211 100644 --- a/website/TRANSLATING.md +++ b/website/TRANSLATING.md @@ -15,13 +15,16 @@ Anything not listed is an ordinary technical word. Translate it the usual way; j | `anchor` | `anchor` | Where the context starts. | | `handoff` | `handoff` | Writes a new anchor. | | `state` | `state` | The dictionary a turn shares. Type `TurnState`. | -| `steering` | `steering` | | +| `steering` | `steering` | Never 引导. | +| `queue up` | `queue up` | Never 排队. | | `hook` | `hook` | Never 钩子. | | `envelope` | `envelope` | Never 信封. | | `trace` | `trace` | Never 轨迹, 追踪. | | `CodeMode` | `CodeMode` | The class. The feature is `code mode`. Never 代码模式. | -| `harness` | `Harness` | The app layer around the loop. Keep the capital H. | +| `extra` | `extra` | User-facing prose says "`app` extra" and "`trace` extra"; keep `extra` for the packaging mechanism, `--extra`, and `package[extra]`. | +| `harness` | `harness` | The app layer around the loop. | +Docs pages explain the `steering` and `queue up` difference where it changes the reader's choice. A steering message that the current turn does not consume falls back to the pending queue; pages that mention steering keep that fallback rule. For the general statement, link [Steering and queueing](https://docs.github.com/en/copilot/how-tos/copilot-sdk/features/steering-and-queueing) instead of translating it. ## Punctuation (zh-cn) diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 0393dfc87..86a586369 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -148,7 +148,7 @@ export default defineConfig({ 'docs/app/workspace', 'docs/app/configure', 'docs/app/terminal', - 'docs/app/channels', + 'docs/app/gateway', ], }, { @@ -158,13 +158,14 @@ export default defineConfig({ items: [ 'docs/app/tools', 'docs/app/skills', + 'docs/app/subagents', 'docs/app/plugins', 'docs/app/hooks', ], }, { - label: 'Ship', - translations: { 'zh-CN': '上线' }, + label: 'Deploy and distribute', + translations: { 'zh-CN': '部署与分发' }, collapsed: false, items: [ 'docs/app/deploy', @@ -188,9 +189,12 @@ export default defineConfig({ label: 'Integrations', translations: { 'zh-CN': '集成' }, items: [ - 'docs/integrations', 'docs/integrations/mcp', - 'docs/integrations/agent-plugins', + { + label: 'Portable Agent Plugins', + translations: { 'zh-CN': '可移植 Agent Plugins' }, + slug: 'docs/integrations/agent-plugins', + }, 'docs/integrations/acp', 'docs/integrations/local-models', 'docs/integrations/storage', @@ -202,8 +206,9 @@ export default defineConfig({ translations: { 'zh-CN': '参考' }, items: [ 'docs/reference/cli', - 'docs/reference/python-api', 'docs/reference/settings', + 'docs/reference/tools', + 'docs/reference/python-api', 'docs/reference/hooks', ], }, diff --git a/website/src/content/docs/docs/app/channels.mdx b/website/src/content/docs/docs/app/channels.mdx deleted file mode 100644 index 7b9b35247..000000000 --- a/website/src/content/docs/docs/app/channels.mdx +++ /dev/null @@ -1,151 +0,0 @@ ---- -title: Channels -description: Choose the channels the gateway starts, tune batching and session ids, and configure the Telegram bot. ---- - -A channel is how messages enter and leave the Bub app. `cli` is the terminal, `telegram` is a bot on Telegram, and a plugin can add another messaging platform or a background runtime. The gateway starts the enabled channels, batches their messages, and keys their sessions. The application framework manages channels. A service that uses an Agent directly reads its own input and writes its own output. - -## Before you begin - -- You have run `bub onboard`, or you wrote `~/.bub/config.yml` yourself. See [Configuration](/docs/app/configure/). -- You have a Telegram bot token from [@BotFather](https://t.me/BotFather), if you want to use Telegram. -- For group chats, **Privacy Mode is off** in BotFather. With it on, the bot sees commands only, and the inbound filter cannot decide whether to answer. - -## Choose channels - -`bub gateway` listens on every enabled channel that has a working config. By default it enables `all`, which expands to every enabled channel that is not an `Interface`. That set includes listeners such as Telegram and `Lifecycle` runtimes such as `mcp.lifecycle`. The builtin `cli` interface is excluded, so a long-running gateway does not take the terminal of the operator. - -Enable one channel: - -```bash -bub gateway --enable-channel telegram -``` - -Enable several: - -```bash -bub gateway --enable-channel telegram --enable-channel wechat -``` - -Exclude a default lifecycle channel: - -```bash -bub gateway --enable-channel all --enable-channel '!mcp.lifecycle' -``` - -To pin the default in the config, set `enabled_channels` (comma-separated, or `all`): - -```yaml -enabled_channels: telegram -``` - -Or use the environment variable: - -```bash -BUB_ENABLED_CHANNELS=telegram -``` - -A channel starts only if its `enabled` property returns `True`. The Telegram channel, for example, returns `False` when `BUB_TELEGRAM_TOKEN` is empty, so the gateway skips it without an error. - -## Debounce and batching - -A channel that is not the CLI can combine a burst of short messages into one turn. These variables control the debounce: - -| Variable | Default | Meaning | -|---|---|---| -| `BUB_DEBOUNCE_SECONDS` | `1.0` | Quiet time after an active message before the batch is flushed. | -| `BUB_MAX_WAIT_SECONDS` | `10.0` | Hard upper bound on how long a follow-up message can extend the wait. | -| `BUB_ACTIVE_TIME_WINDOW` | `60.0` | Window in which other messages still count as part of the active conversation. | - -Bub stores the pending messages of each session, and it merges them into one `ChannelMessage` when the timer fires. - -:::tip -Comma commands (input that starts with `,`) bypass the debounce. They are dispatched the moment they arrive. This is useful for `,quit`, `,help`, or a tool name. -::: - -## Session ids - -Bub keys conversation state by `session_id`. The default rule is `:`: - -- `bub run "..."` (no `--session-id`) gives `cli:local`. -- A Telegram inbound message gives `telegram:`. -- The `bub chat` REPL gives `cli_session`, a literal string that does not follow the pattern. - -Use `--session-id` on `bub run` or `bub chat` to share state across surfaces. - -## Builtin channels - -| Channel | On by default | Debounce | -|---|---|---| -| `cli` | only through `bub chat` | no | -| `telegram` | yes, when `BUB_TELEGRAM_TOKEN` is set | yes | - -A plugin can register more channels through the `provide_channels` hook. See [Plugins](/docs/app/plugins/). - -## Telegram - -The Telegram channel is registered as `telegram`. It uses long polling through `python-telegram-bot`. It turns on at runtime when `BUB_TELEGRAM_TOKEN` is set. - -### Set the token - -```bash -BUB_TELEGRAM_TOKEN=123456:abcdef... -``` - -This one variable is the switch. When it is empty, the channel reports `enabled=False`, and `bub gateway` skips it without an error. - -### Restrict who can use the bot - -The allowlists are comma-separated, and usernames are case-sensitive: - -```bash -BUB_TELEGRAM_ALLOW_USERS=123456789,your_username -BUB_TELEGRAM_ALLOW_CHATS=-1001234567890,-1009876543210 -``` - -The two lists behave in different ways: - -- With `BUB_TELEGRAM_ALLOW_CHATS` set, messages from any other chat are dropped silently. -- With `BUB_TELEGRAM_ALLOW_USERS` set, other users receive `Access denied.` -- A user matches when either the numeric id or the username is in the list. - -:::caution -If both lists are empty, anyone who finds the bot can drive it. For a production deployment, set at least one. -::: - -### Route through a proxy - -```bash -BUB_TELEGRAM_PROXY=http://127.0.0.1:7890 -# or socks5://user:pass@host:port -``` - -The same proxy serves the polling connection and the outbound API calls. - -### Start the gateway - -```bash -bub gateway --enable-channel telegram -``` - -`--enable-channel telegram` pins the listener to one channel. Without it, `bub gateway` enables every non-`Interface` channel in the default set, which includes Telegram when its token is set, and it also starts the enabled `Lifecycle` runtimes. On startup you should see: - -```text -telegram.start allow_users_count=2 allow_chats_count=1 proxy_enabled=False -telegram.start polling -``` - -### Inbound messages - -The channel uses `telegram:` as the session id. A private chat and a group chat stay separate, even when the same user is in both. - -- `/start` is handled internally. The bot replies with a fixed greeting, or with the "not allowed" message in a chat that is not in the allowlist. -- `/bub ` is normalized. The `/bub ` prefix is removed, and the rest becomes the prompt. -- In a private chat, every message that is not a command is ingested. -- In a group chat, every message that is not a command reaches the channel. A message is active when it mentions `@`, contains the keyword `bub`, or replies to a message from the bot. Other group messages are ignored, unless a conversation for that session is open within `BUB_ACTIVE_TIME_WINDOW`. In that case they join the batch. - -### Outbound messages - -A normal inbound message sets `output_channel="null"` before it enters the turn pipeline. A plain model answer is therefore not sent back blindly. The bundled `telegram` skill is the normal path for replies, progress updates, and edits. - -Comma commands are different. They keep `output_channel="telegram"`, so their tool output goes back to the originating `chat_id`. When the content is JSON with a `message` field, only that field is sent. Otherwise the raw content is sent. An empty payload is dropped. diff --git a/website/src/content/docs/docs/app/configure.mdx b/website/src/content/docs/docs/app/configure.mdx index 781254d52..958b6d4cc 100644 --- a/website/src/content/docs/docs/app/configure.mdx +++ b/website/src/content/docs/docs/app/configure.mdx @@ -1,74 +1,85 @@ --- title: Configuration -description: Set the Bub application's model, provider credentials, and channels. +description: How to configure the Bub application. --- -Choose the model the Bub application calls and the channels it connects to. These settings belong to the application. When you construct `core.Agent` directly, pass its model and components in Python; see [Build your own agent](/docs/sdk/). +import { Aside } from '@astrojs/starlight/components'; -Bub reads its own configuration from these places, in order of precedence (highest first): +Bub reads configuration in this order, highest priority first: -1. Process environment (`BUB_*` variables). -2. `.env` values loaded during CLI startup or by settings classes that explicitly declare `env_file=".env"`. -3. `~/.bub/config.yml` (created by `bub onboard`). +1. Environment variables (the `BUB_` prefix); +2. `.env`, loaded from the current workspace at CLI startup, or read by settings classes that declare `env_file=".env"`; +3. `~/.bub/config.yml`, created by `bub onboard`. -When no explicit Bub key is supplied, Bub reads `_API_KEY` and `_API_BASE`, using Republic provider names with hyphens replaced by underscores. Examples include `OPENROUTER_API_KEY`, `GOOGLE_API_KEY`, and `AZURE_OPENAI_API_KEY`. Explicit `BUB_*` settings take precedence. +Fields not declared in the configuration use the settings class defaults. See [Settings › Full example](/docs/reference/settings/#example) for every writable field. -## Before you begin + -`bub onboard` guides you through provider selection, API key and URL (when needed), connection checking, searchable model selection, channels, and streaming settings. Hosted providers use their default URL; choose **OpenAI-compatible** for a custom OpenAI-compatible server. Leave the API key blank to use environment credentials. If the model list is unavailable, you can edit the connection, retry, or enter a model ID manually. The connection check fetches models without generating a completion. Selecting Codex skips model discovery and uses manual model entry. The collected configuration replaces `~/.bub/config.yml`; existing values supply prompt defaults and are preserved unless updated. Plugins may add prompts through the `onboard_config` hook. +## 1. Finish the initial setup + +Run the following command in your terminal to start the interactive setup. When it finishes, it writes the result to `~/.bub/config.yml`. ```bash bub onboard ``` -The config file defaults to `~/.bub/config.yml`. `BUB_HOME` sets a separate path for runtime data: command history, tapes, and the managed plugin project. Use `BubFramework(config_file=...)` when embedding Bub and you need a different config path. - -:::note -`bub onboard` only collects what the loaded plugins ask for via the `onboard_config` hook. If you install a plugin that adds new settings, run `onboard` again. -::: +## 2. Check the config file -## 2. Inspect the config file - -A minimal config looks like this: +Now check whether `~/.bub/config.yml` contains the content described in the [full example](/docs/reference/settings/#example). Here is an example: ```yaml # ~/.bub/config.yml model: openrouter:openrouter/free -api_key: sk-or-v1-... +api_key: sk-or-... telegram: token: "123456:abcdef..." allow_users: "123456789,your_username" ``` -Top-level keys map to `AgentSettings` in [src/bub/builtin/settings.py](https://github.com/bubbuild/bub/blob/main/src/bub/builtin/settings.py). Per-channel keys (such as `telegram:`) map to that channel's `Settings` subclass. - -## 3. Override at runtime with environment variables +## 3. Override with environment variables -Override application settings with environment variables. Common settings use the `BUB_` pattern: +**By default, your key is written directly to the config file in plain text.** If you have higher security requirements and don't want provider API keys stored locally in plain text, you can set or override them with environment variables when running Bub. The relevant variables are: -| Variable | Purpose | +| Variable | Description | |---|---| -| `BUB_MODEL` | Model identifier, e.g. `openrouter:openrouter/free` or `codex:`. | -| `BUB_API_KEY` | API key for the active provider. Leave unset when using `bub login codex`. | -| `BUB_API_BASE` | Override the provider's base URL. Leave unset to use the selected provider's default endpoint. | -| `BUB_HOME` | Bub's runtime data directory (history, tapes, managed plugin project). Default `~/.bub`; does not move the default config file. | -| `BUB__API_KEY` | Per-provider key, e.g. `BUB_OPENROUTER_API_KEY`. | -| `BUB__API_BASE` | Per-provider base URL. | +| `BUB_MODEL` | Model identifier, such as `openrouter:openrouter/free` or `codex:`. | +| `BUB_API_KEY` | API key for the current provider; not needed with `bub login codex`. | +| `BUB_API_BASE` | Provider service URL; leave it blank for the default endpoint. | +| `BUB_HOME` | Runtime data directory, default `~/.bub`. | +| `BUB__API_KEY` | Key for a specific provider, such as `BUB_OPENROUTER_API_KEY`. | +| `BUB__API_BASE` | Service URL for a specific provider. | + +See the [settings reference](/docs/reference/settings/) for the full table. + +## 4. Configure the model and fallback models + +`model` uses the `provider:model` format. To try fallback models in order when the primary model is unavailable, configure this in `~/.bub/config.yml`: -Republic reads and refreshes Codex file credentials from `$CODEX_HOME/auth.json` or `~/.codex/auth.json`. Install the Codex CLI for a new login, then select `codex:` and leave `BUB_API_KEY` unset. Use `openai:` with API credentials for OpenAI API access. +```yaml +model: openai:gpt-5-mini +fallback_models: + - openrouter:openrouter/free +``` -To reach an endpoint that speaks a supported API under a name of its own (a company proxy, a relay, a second account), declare it under `providers` and use that name as the model prefix; see [Custom providers](/docs/reference/settings/#custom-providers). +Each candidate provider needs its own valid credentials. Fallbacks only cover failures while creating a client and opening a request; an error after the stream has started does not replay on another model, to avoid duplicate side effects. The fallback set is carried only when the current model identifier matches `model` in the configuration; it does not apply after you switch to another model. See [Models and fallback](/docs/reference/settings/#model-routing) for the full rules. -For the full table, including channel debounce knobs and advanced model-client settings, see [Settings reference](/docs/reference/settings/). +In `bub chat`, you can switch only the current session: + +```text +,model openai:gpt-5-mini +,reasoning_effort high +``` -## 4. Configure channel credentials +Both settings take effect from the next Turn and are saved in the current session's Tape. The available reasoning effort values depend on the provider. -Each channel has its own settings prefix. The builtin Telegram channel reads `BUB_TELEGRAM_*`: +## 5. Configure the Telegram channel (optional) + +If you want to use Bub as a Telegram bot, use the built-in Telegram channel. Example configuration: ```bash BUB_TELEGRAM_TOKEN=123456:abcdef... @@ -77,14 +88,15 @@ BUB_TELEGRAM_ALLOW_CHATS=-1001234567890 BUB_TELEGRAM_PROXY=http://127.0.0.1:7890 # optional ``` -For the message-handling and access-control behavior these variables drive, see [Channels](/docs/app/channels/#telegram). +See [Telegram channel](/docs/app/gateway/#telegram) for more configuration options. -## 5. Verify +## 6. Verify -Run one conversation turn with the configured model: +Now try talking to Bub: ```bash -bub run "say hi" -``` +$ bub run "Can you introduce yourself?" -The command prints a routing label (`[:]`) followed by the response. If you need to check whether a configuration plugin loaded, inspect it with `bub hooks`. +[cli:local] +I'm a virtual assistant designed to support a wide range of tasks including coding, design, research, and automation. I can integrate with code repositories, access web resources, leverage specialized skills, and streamline workflows. If you have specific tasks or questions about my capabilities, feel free to ask! +``` diff --git a/website/src/content/docs/docs/app/deploy.mdx b/website/src/content/docs/docs/app/deploy.mdx index f55b8162b..c815c7885 100644 --- a/website/src/content/docs/docs/app/deploy.mdx +++ b/website/src/content/docs/docs/app/deploy.mdx @@ -1,25 +1,34 @@ --- title: Deployment -description: Run the Bub application with Docker Compose and keep its data across restarts. +description: Run the Bub gateway with Docker Compose, persist its data, and upgrade safely. --- -Run the Bub application in a container, mount its data on the host, and choose the gateway channels it starts. Download and unpack the source archive for Bub 0.6.0 from [GitHub Releases](https://github.com/bubbuild/bub/releases), then run the commands below from that directory. +Bub can run as a long-lived container. The official image includes the runtime, common command-line tools, and the managed plugin environment; the host only needs to provide `.env`, a workspace, and a data directory. -The repository ships three files that drive the canonical deployment: [`Dockerfile`](https://github.com/bubbuild/bub/blob/main/Dockerfile), [`docker-compose.yml`](https://github.com/bubbuild/bub/blob/main/docker-compose.yml), and [`entrypoint.sh`](https://github.com/bubbuild/bub/blob/main/entrypoint.sh). +There are two ways to get started: -## Before you begin +- **Use a release image**: download [`docker-compose.yml`](https://github.com/bubbuild/bub/blob/main/docker-compose.yml) and [`env.example`](https://github.com/bubbuild/bub/blob/main/env.example) from the repository. No source code is needed. +- **Build locally**: get a source archive from [GitHub Releases](https://github.com/bubbuild/bub/releases), or clone the repository, then build with the same Compose file. -- Docker (or compatible runtime) and `docker compose` are installed. -- You have a `.env` with at minimum `BUB_MODEL` and either `BUB_API_KEY` or a provider-specific key: see [Configure](/docs/app/configure/). -- For a Telegram deployment, `BUB_TELEGRAM_TOKEN` and an allowlist are in `.env`: see [Channels](/docs/app/channels/#telegram). +The image is defined by [`Dockerfile`](https://github.com/bubbuild/bub/blob/main/Dockerfile) and [`entrypoint.sh`](https://github.com/bubbuild/bub/blob/main/entrypoint.sh) in the repository. -## 1. Prepare the env file +## Before you start {#before-you-start} + +- Install Docker and Compose v2, and confirm that `docker compose version` works. +- Prepare the model configuration. `.env` must contain at least `BUB_MODEL`, plus either `BUB_API_KEY` or a provider-specific key. See [Configure](/docs/app/configure/) for the fields. +- To run the Telegram gateway, prepare `BUB_TELEGRAM_TOKEN` and a user or group allowlist. See [Telegram gateway](/docs/app/gateway/#telegram). + +The container runs as `root`. If a host directory was created by another user, first confirm that the Docker process can read and write that path. + +## 1. Prepare the configuration {#configure} + +Create `.env` in the directory that contains the Compose file: ```bash cp env.example .env ``` -A minimum `.env` for a Telegram gateway: +A minimal Telegram configuration: ```bash BUB_MODEL=openrouter:openrouter/free @@ -28,9 +37,13 @@ BUB_TELEGRAM_TOKEN=123456:abcdef... BUB_TELEGRAM_ALLOW_USERS=123456789 ``` -`docker compose` loads `.env` automatically; see the `env_file:` line in `docker-compose.yml`. +In production, set `BUB_TELEGRAM_ALLOW_CHATS` or `BUB_TELEGRAM_ALLOW_USERS` as well. Leaving the allowlist blank lets anyone who finds the bot start a session. + +`docker compose` reads `.env` and passes it to the container. Do not commit an `.env` that contains tokens. + +## 2. Understand the mounts {#volumes} -## 2. Understand the volumes +By default, the Compose file uses three bind mounts: ```yaml volumes: @@ -39,17 +52,19 @@ volumes: - ${BUB_AGENT_HOME:-${HOME}/.agents}:/root/.agents ``` -| Mount | Purpose | -|---|---| -| `/workspace` | The directory Bub treats as the working repo. Defaults to the directory you ran `docker compose` from. | -| `/data` | Host bind for Bub's home directory (history, tapes, plugin project). | -| `/root/.agents` | Shared skill directory (the `agents.md` convention). | +| Container path | Contents | Host variable | +| --- | --- | --- | +| `/workspace` | The working directory for Bub's tools | `BUB_WORKSPACE_PATH`, default current directory | +| `/data` | Tape, command history, managed plugin project, code mode temporary files | `BUB_HOME_HOST`, default `~/.bub` | +| `/root/.agents` | Global Agent skills | `BUB_AGENT_HOME`, default `~/.agents` | -The compose file sets `BUB_HOME=/data` inside the container. Use `BUB_HOME_HOST` when you want the host-side source path to be somewhere other than `~/.bub`. Runtime settings for this deployment should live in `.env`; `BUB_HOME` does not move Bub's default `config.yml` path. +Compose sets `BUB_HOME=/data` inside the container. `BUB_HOME` changes the root of Bub's runtime data; it **does not move** the default `~/.bub/config.yml`. The configuration file path in the container is still resolved from the container user's home directory. Prefer writing non-sensitive settings to `.env`. -## 3. Pull and start +The workspace mount determines which files the model and tools can see. Do not mount the host root, or any directory that contains secrets, as `/workspace`. -`docker-compose.yml` uses the prebuilt image `ghcr.io/bubbuild/bub`, published to GHCR on every release: +## 3. Start {#start} + +With a release image: ```bash docker compose pull @@ -57,60 +72,103 @@ docker compose up -d docker compose logs -f app ``` -Image tags follow the release version (`0.6.0a2`), `.` for stable releases, and `latest` for the newest stable release. Set `BUB_IMAGE` in `.env` to pin a version, for example `BUB_IMAGE=ghcr.io/bubbuild/bub:0.6.0a2`. +Compose uses `ghcr.io/bubbuild/bub:latest` by default. The release workflow also produces: + +- a full version tag, such as `0.6.0`, or a prerelease tag such as `0.6.0a2`; +- a `.` tag for stable releases, such as `0.6`; +- updates to `latest` only for stable releases. -To build from your local checkout instead, pass `--build`. The local build is tagged as `BUB_IMAGE`, so it replaces the pulled image until the next `docker compose pull`: +For reproducible deployments, pin `BUB_IMAGE` to a full version: + +```bash +BUB_IMAGE=ghcr.io/bubbuild/bub:0.6.0 +``` + +To build locally: ```bash docker compose up -d --build ``` -A local build takes a few minutes — the image installs system tools (git, node, jq, etc.) and runs `uv sync --no-dev --no-editable --extra app`. +A local build installs system tools and runs: + +```bash +uv sync --locked --no-dev --no-editable --extra app +``` + +The first build usually takes a few minutes. The built image uses the `BUB_IMAGE` tag from the Compose file; if that tag matches a remote tag of the same name, a later `docker compose pull` pulls the remote image again. During development you can use your own tag, for example `BUB_IMAGE=bub-local:dev`. -### Run without Compose +### Without Compose {#docker-run} -The image runs the same way with plain `docker run`. Mount the same volumes and pass `.env` explicitly: +Running the image directly uses the same mounts and start command as Compose: ```bash docker run -d --name bub --restart unless-stopped \ --env-file .env \ -e BUB_HOME=/data \ -v "$PWD":/workspace \ - -v ~/.bub:/data \ - -v ~/.agents:/root/.agents \ + -v "$HOME/.bub":/data \ + -v "$HOME/.agents":/root/.agents \ ghcr.io/bubbuild/bub:latest + docker logs -f bub ``` -To upgrade, `docker pull` the new tag, then `docker rm -f bub` and rerun the same command. +`docker run` does not read the Compose `environment` section automatically, so `BUB_HOME=/data` is passed explicitly here. To upgrade, pull the new tag, remove the old container, and recreate it with the same volumes; do not delete `/data` before upgrading. + +## 4. What the startup script does {#entrypoint} -## 4. What `entrypoint.sh` does +On every container start, `entrypoint.sh` does the following in order: -On every container start: +1. If `/workspace/bub-reqs.txt` exists, install the Python packages listed in it into the image's virtual environment. +2. Activate `/app/.venv` and run `bub install` with no arguments, which syncs the managed plugins in `/data/bub-project`. +3. If `/workspace/startup.sh` exists, run it with `bash`; otherwise run `bub gateway`. -1. If `/workspace/bub-reqs.txt` exists, install the listed packages into the image's venv. -2. Run `bub install` with no arguments; this syncs the managed plugin project under `/data/bub-project/`. New plugin entries in `pyproject.toml` are picked up here. -3. If `/workspace/startup.sh` exists, exec it. Otherwise exec `bub gateway`. +As a result, plugins added by `bub install` survive container restarts. To pin extra dependencies in the workspace, commit `bub-reqs.txt`; it suits packages in the image that cannot be declared through a plugin project, but it lengthens every startup. -This means a workspace can override the entrypoint by providing its own `startup.sh`; useful when you want to run `bub chat` against a long-lived workspace, or to customize gateway flags: +Use `startup.sh` to override the default entrypoint: ```bash +#!/bin/bash # /workspace/startup.sh +set -euo pipefail exec /app/.venv/bin/bub gateway --enable-channel telegram ``` -## 5. Verify the running service +The script must `exec` the final long-lived process; otherwise container signals stop at the shell. To customize the channel combination, pass `--enable-channel` here, or use `BUB_ENABLED_CHANNELS`. + +## 5. Verify {#verify} ```bash docker compose ps -docker compose logs -f app | rg "telegram.start polling" +docker compose logs -f app ``` -A healthy Telegram deployment logs `telegram.start polling` once. If there are no channel logs, check that the container received the token without printing it: `docker compose exec app sh -c 'test -n "$BUB_TELEGRAM_TOKEN"'` returns status 0 when it is set. +A healthy Telegram start logs something like: -## 6. Upgrade safely +```text +telegram.start polling +``` -Back up the host data directory before upgrading. Then pull the new image and recreate the container, keeping `.env` and the host mount paths. The bind mount at `/data` retains the plugin project (host `BUB_HOME_HOST`, default `~/.bub`): +Check that a variable was passed in without printing its value: + +```bash +docker compose exec app sh -c 'test -n "$BUB_TELEGRAM_TOKEN" && echo token-present' +``` + +If the container keeps restarting, first look for plugin import failures or configuration validation errors in the logs. A failed `bub install` stops `entrypoint.sh` from continuing, which avoids starting the gateway with half-installed plugins. + +## 6. Upgrade and roll back {#upgrade} + +Before upgrading, back up `/data` and `.env`. Tape is append-only JSONL; stop writes during the backup, or use a filesystem snapshot: + +```bash +docker compose stop app +tar -czf "bub-data-$(date +%Y%m%d-%H%M%S).tar.gz" "$HOME/.bub" +docker compose start app +``` + +To upgrade with a release image: ```bash docker compose pull @@ -118,19 +176,33 @@ docker compose up -d docker compose logs -f app ``` -If you build locally, run `git pull && docker compose build --pull` instead of `docker compose pull`. +To upgrade a local build: + +```bash +git pull +docker compose build --pull +docker compose up -d +``` + +To roll back to a pinned version, switch back to the old tag: + +```bash +BUB_IMAGE=ghcr.io/bubbuild/bub:0.6.0 docker compose up -d +``` + +If the new version changed plugin dependencies or the data format, restore the matching `/data` backup as well. Do not assume that the old code can read all the data the new version has written. + +### Manage plugins inside the container {#plugins} -Inside the running container, plugins are managed by `bub install` and `bub update`: +The managed plugin project lives at `/data/bub-project`. The following commands affect only that virtual environment and do not modify the system Python: ```bash -docker compose exec app bub install # sync project -docker compose exec app bub install owner/repo@v1.2.0 # add or pin a plugin -docker compose exec app bub update # upgrade everything -docker compose exec app bub update specific-plugin # upgrade one +docker compose exec app bub install +docker compose exec app bub install https://github.com/owner/repo.git@v1.2.0 +docker compose exec app bub update +docker compose exec app bub update specific-plugin ``` -Both commands operate on the managed project at `BUB_PROJECT` (default `bub.home / "bub-project"`, `/data/bub-project` in this compose deployment). They never touch the system Python environment. +`bub install` with no arguments syncs according to the project declarations; with arguments it adds or pins a plugin. `bub update` upgrades all packages, or a named package. After changing plugins, restart the container, or rerun the startup script, so the gateway loads the new entry points. -:::note -`bub install` and `bub update` require Bub itself to live in a virtual environment. The container image satisfies this; the `entrypoint.sh` activates `/app/.venv` before exec. -::: +`bub install` and `bub update` require Bub itself to live in a virtual environment. The official image satisfies this, and `entrypoint.sh` activates `/app/.venv` first. diff --git a/website/src/content/docs/docs/app/distribution.mdx b/website/src/content/docs/docs/app/distribution.mdx index fc034ca39..33b2a13df 100644 --- a/website/src/content/docs/docs/app/distribution.mdx +++ b/website/src/content/docs/docs/app/distribution.mdx @@ -1,15 +1,23 @@ --- -title: Distribution -description: Embed the application framework or ship a CLI with your own plugins and skills. +title: Embedding and distribution +description: Run a complete Turn inside an existing service, or publish a Bub distribution that includes plugins, skills, and your own command. --- -Build on the Bub app when you want its configuration, plugins, and message routing in your product. You can host `BubFramework` in an existing process or ship a command that starts it. +When you need Bub's configuration, plugins, sessions, and message routing, reuse the full application instead of reassembling `core.Agent`. Choose your entry point by goal first: -This path uses the application dependencies in [Install Bub](/docs/getting-started/install/). For a program that directly composes an Agent and its components, follow [Build your own agent](/docs/sdk/). +| You need | Entry point | +| --- | --- | +| Only reuse tool implementations | Import the public components and pass them to `core.Agent`; see [Reuse application plugins](/docs/sdk/reuse-plugins/) | +| Reuse interception, system prompts, or steering | `AgentPlugins.attach()`; see [Reuse application plugins](/docs/sdk/reuse-plugins/) | +| Use the application's default configuration, skills, and tool policies | `framework.running()` + `BubAgent`; see [Reuse application plugins](/docs/sdk/reuse-plugins/) | +| Run a full message Turn | `process_inbound()`; continue on this page | +| Publish your own CLI or wheel | See [Publish your own command](#publish-a-command) | -## Embed the application +This path requires the [application dependencies](/docs/getting-started/install/#app-install). If you only need the model loop and tools, start from [Run an agent in Python](/docs/sdk/). -Use `BubFramework.process_inbound` to run the application's full turn: resolve the session, load state, prepare the prompt, execute, save state, render, and dispatch. +## Embed the full Turn {#embed-the-application} + +`process_inbound()` runs the complete Turn, from resolving the session to sending the reply: ```python import asyncio @@ -21,6 +29,7 @@ from bub.envelope import content_of async def main() -> None: framework = BubFramework() framework.load_hooks() + async with framework.running(): result = await framework.process_inbound({ "session_id": "embedded:demo", @@ -28,28 +37,51 @@ async def main() -> None: "chat_id": "demo", "content": "Describe what you can help with.", }) - for outbound in result.outbounds: - print(content_of(outbound)) + + print(result.session_id) + for outbound in result.outbounds: + print(content_of(outbound)) asyncio.run(main()) ``` -Configure a model before running this example. `load_hooks()` loads builtin hooks and installed plugin entry points. The sample prints returned envelopes; a connected channel or plugin can also dispatch them. +Configure a model before running this. `load_hooks()` loads the builtin implementations and installed plugins. The example does not bind a channel router, so replies stay in `result.outbounds` and the host sends them itself. + +`process_inbound()` returns a `TurnResult`, not an event stream. With `stream_output=True`, it consumes the event stream through the bound router and still returns the complete result when it finishes. + +`framework.running()` should cover the service's entire lifetime, not be entered once per request. It holds `provide_lifespan`, Tape storage, and the steering inbox (`SteeringInbox`); create objects that need these resources after entering the scope. Do not enter multiple overlapping scopes on the same Framework. + +The framework has no default session lock. When concurrent requests share a session, serialize them by `session_id`, or explicitly allow concurrency through the channel admission policy. A Turn is not a transaction either: tool side effects are not rolled back if a later step fails. + +### Bind a sending channel {#bind-a-router} + +If the host already has sending logic, just read `outbounds`. To let a channel handle outbound messages and streaming output, implement `ChannelRouter` and bind it: -Keep `framework.running()` open for the service's lifetime. It acquires hook-provided resources and runs the cleanup supplied by their context managers on exit. Enter it before creating an agent that needs the framework's store, and avoid overlapping scopes on one framework instance. +```python +class HostRouter: + async def dispatch_output(self, message) -> bool: + await send_to_host(message) + return True + + def wrap_stream(self, message, stream): + return stream -`process_inbound` returns a completed `TurnResult`. With `stream_output=True`, it consumes the stream through the bound channel router before returning that result. The host owns concurrent message scheduling and any per-session locking it requires. + async def quit(self, session_id) -> None: + ... -## Reuse plugins in your own service + async def admit_channel_message(self, session_id, message, turn): + return None -A host can use plugin-provided tools, stores, or hooks while keeping its own API and stream handling. Follow [Reusing plugins](/docs/sdk/reuse-plugins/) to connect those capabilities to `core.Agent`. -`BubAgent` also carries the application's prompts and channel delivery conventions. Choose it when you want those application behaviors, and provide the corresponding output handling in your host. +framework.bind_channel_router(HostRouter()) +``` + +`dispatch_output` returns whether it handled the message; `wrap_stream` can wrap the model stream; `admit_channel_message` returns an admission decision, and `None` means use the default scheduling. Pass `None` to unbind. -## Ship your own command +## Publish your own command {#publish-a-command} -A package can expose Bub's command entry point under its own name: +A distribution package can provide its own command name and call the Bub entry point internally: ```python # src/my_bub/cli.py @@ -64,13 +96,13 @@ def main() -> None: my-bub = "my_bub.cli:main" ``` -Users can then run `my-bub chat` or `my-bub gateway`. Set a product-specific working directory before calling Bub if your application requires one; the framework takes its initial workspace from the current directory. +After installation, users can run `my-bub chat` or `my-bub gateway`. Declare `dependencies = ["bub[app]"]`, and follow the package restructuring described in the [0.6 migration notes](/docs/migration/v0-6/). -Declare `bub[app]>=0.6.0` as the application dependency. When upgrading an existing distribution, follow [Upgrade to 0.6.0](/docs/migration/v0-6/). +To pin a workspace, change the current directory before calling Bub, or let users pass `--workspace`. -## Bundle plugins and skills +## Bundle plugins and skills {#bundle-plugins-and-skills} -One wheel can provide several entry points: +One wheel can provide multiple entry points and skill directories: ```toml [project.entry-points."bub"] @@ -81,6 +113,18 @@ channels = "my_bub.channels:ChannelPlugin" packages = ["src/my_bub", "src/skills"] ``` -Each target follows the [plugin registration rules](/docs/app/plugins/). Include task instructions using the [skills package layout](/docs/app/skills/#include-skills-in-a-wheel). +Entry points follow the [plugin registration rules](/docs/app/plugins/#entry-point-forms). For the skill directory layout, see [Distribute skills with a Python package](/docs/app/skills/#include-skills-in-a-wheel). + +Plugin dependencies should be declared in the distribution package. To install a plugin into the separate project that Bub manages, use [`bub install`](/docs/reference/cli/#bub-install). + +## Verify before release {#verify-a-release} + +Run the following checks in a separate virtual environment: + +1. Install from the wheel instead of running from the source directory. +2. Run `bub hooks` and confirm that the entry points load without deprecation warnings. +3. Run `bub run` to verify the model, tools, and state. +4. Run `bub chat` to verify skills, commands, and lifecycle channels. +5. For a host service, verify concurrency, stream closure, and shutdown cleanup. -Before release, install the built wheel into a separate environment. Run the command, confirm the plugin report, load a packaged skill, and exercise a representative conversation. For a hosted application, also check shutdown while a stream or background tool is active. +A clean environment exposes undeclared dependencies, entry points that cannot be imported, and configuration that only works in the development directory. diff --git a/website/src/content/docs/docs/app/gateway.mdx b/website/src/content/docs/docs/app/gateway.mdx new file mode 100644 index 000000000..1b0baa3a7 --- /dev/null +++ b/website/src/content/docs/docs/app/gateway.mdx @@ -0,0 +1,288 @@ +--- +title: Gateway +description: Configure and run the Bub gateway, connect chat platforms, and manage messages and sessions. +--- + +import { Steps } from '@astrojs/starlight/components'; + +`bub gateway` runs the enabled channels continuously and hands platform messages to [Turn](/docs/concepts/turn/) for processing. Bub ships with a Telegram channel; terminal interaction uses [`bub chat`](/docs/app/terminal/), and other platforms connect through plugins. + +## Start the Telegram gateway {#telegram} + +First make sure `bub chat` can reply normally, and get a bot token from [@BotFather](https://t.me/BotFather). See [Configuration](/docs/app/configure/) for model settings. + + + +1. **Configure `.env`** + + Set these in `.env` at the workspace root: + + ```bash + BUB_TELEGRAM_TOKEN=123456:abcdef... + BUB_TELEGRAM_ALLOW_USERS=123456789 + ``` + + Keep the model settings such as `BUB_MODEL` and `BUB_API_KEY` in the file. Set `BUB_TELEGRAM_ALLOW_USERS` to your numeric ID; you can look it up with [@userinfobot](https://t.me/userinfobot). + + Pass variables through `.env` or `export`. Writing `BUB_TELEGRAM_TOKEN=...` in the shell without exporting it does not pass it to a `bub` process started later. + +2. **Start the gateway** + + Run it in the workspace that contains `.env`: + + ```bash + bub gateway + ``` + + Example log from a successful startup: + + ```text + telegram.start allow_users_count=1 allow_chats_count=0 proxy_enabled=False + telegram.start polling + ``` + +3. **Verify a private chat** + + Send the bot a private message. While the Turn runs, the chat window shows typing. If there is no reply, see [Configure replies](#telegram-outbound) and [Troubleshooting](#troubleshooting). + + + +The Telegram channel is enabled whenever `BUB_TELEGRAM_TOKEN` is non-empty. The gateway needs at least one enabled channel. + +## Configure Telegram + +### Restrict access + +`BUB_TELEGRAM_ALLOW_USERS` restricts users, and `BUB_TELEGRAM_ALLOW_CHATS` restricts chats. Separate multiple values with commas; usernames are case-sensitive: + +```bash +BUB_TELEGRAM_ALLOW_USERS=123456789,your_username +BUB_TELEGRAM_ALLOW_CHATS=-1001234567890,-1009876543210 +``` + +| Allowlist | Pass condition | When it fails | +| --- | --- | --- | +| `BUB_TELEGRAM_ALLOW_USERS` | Numeric ID or username matches | The user receives `Access denied.` | +| `BUB_TELEGRAM_ALLOW_CHATS` | Chat ID matches | The message is dropped silently | + +When both are set, both must pass. When both are empty, anyone can use the bot; set at least one before deploying it publicly. + +### Configure group chats {#telegram-inbound} + +Turn off **Privacy Mode** in BotFather. Otherwise the bot only sees commands and cannot read ordinary group messages. + +| Message source or type | Received when | +| --- | --- | +| Non-command messages in a private chat | Always | +| Text messages in a group chat | Mentions `@`, contains `bub`, or replies to the bot | +| Images and files with text captions in a group chat | Same as text messages | +| Non-text messages without captions in a group chat | Only when replying to the bot | +| `/bub ` | The `/bub ` prefix is removed and the rest becomes the prompt | + +Group messages that do not meet these conditions are ignored. If the session is still within `BUB_ACTIVE_TIME_WINDOW`, those messages may also join the current batch. `bub` uses substring matching, so `bubble` also matches. + +### Configure replies {#telegram-outbound} + +| Input | `output_channel` | How it is sent | +| --- | --- | --- | +| Ordinary message | `"null"` | The built-in `telegram` skill sends replies, progress, and edits | +| Comma command | `"telegram"` | Tool output goes to the originating `chat_id` | + +The model output for an ordinary message is not sent to Telegram automatically; the model must call the `telegram` skill, or the user receives no reply. Typing is renewed every 4 seconds and only means the Turn has not finished. + +When a comma command's output is JSON with a `message` field, only that field is sent; other non-empty output is sent as-is, and empty output is dropped. + +### Merge consecutive messages + +Telegram merges consecutive messages sent within a short period into one Turn: + +| Variable | Default | Description | +| --- | --- | --- | +| `BUB_DEBOUNCE_SECONDS` | `1.0` | Short wait that is reset by each active message | +| `BUB_MAX_WAIT_SECONDS` | `10.0` | Upper bound on waiting for inactive follow-up messages | +| `BUB_ACTIVE_TIME_WINDOW` | `60.0` | Time window in which inactive messages can join the batch | + +Messages explicitly addressed to the bot in private and group chats are active; subsequent unmentioned group messages in the same session are inactive. Both are staged per session and merged with newlines once the timer ends. Input that starts with the command prefix (`,` by default) does not participate in merging and is dispatched immediately. + +### Session IDs + +The default session format is `:`: + +| Entry | Default `session_id` | +| --- | --- | +| `bub run "..."` | `cli:local` | +| `bub chat` | `cli_session` | +| Telegram | `telegram:` | + +Different entries do not share history by default. To continue a conversation across entries, explicitly use the same `--session-id`: + +```bash +bub chat --session-id shared:demo +bub run --session-id shared:demo "Continue the previous topic" +``` + +### Media, proxy, and polling {#telegram-reference} + +| Message type | Text that enters the context | +| --- | --- | +| Image | `[Photo message] Caption: ...` | +| Sticker | `[Sticker: 😀 from sticker_set]` | +| Voice / audio | `[Voice message: 12s]` / `[Audio: artist - title (12s)]` | +| Video / video message | `[Video: 12s]` / `[Video note: 12s]` | +| File | `[Document: report.pdf (application/pdf)]` | + +Images, audio, and video are passed to the model as Republic media content; the model must support the corresponding modality. Ordinary files only provide the text description above and are not passed to the model as media attachments. Media that needs downloading and exceeds 2 MB is skipped. When replying to a message, the replied-to content is added to the context through `reply_to_message`. + +Polling and the Telegram API share the same proxy: + +```bash +BUB_TELEGRAM_PROXY=http://127.0.0.1:7890 +# socks5://user:@host:port is also supported +``` + +See [TelegramSettings](/docs/reference/settings/#telegram-settings) for the full fields. The channel handles only `message` updates; at startup it discards updates that accumulated while it was down and does not replay offline messages. + +## Choose channels + +The default `all` starts every entry whose `enabled` is true and whose type is not `Interface`. Channels decide enablement from credentials; Telegram checks the token. + +`all` also includes background services such as `mcp.lifecycle`. When you name chat channels explicitly, enabled background services still start; exclude them with `!name`: + +```bash +bub gateway --enable-channel telegram --enable-channel '!mcp.lifecycle' +bub gateway --enable-channel telegram --enable-channel discord +``` + +Quote exclusions so the shell does not treat `!` as history expansion. The built-in `cli` is an `Interface` and does not start with the gateway. + +You can also specify channels in the configuration: + +```yaml +# config.yml +enabled_channels: telegram +``` + +```bash +BUB_ENABLED_CHANNELS=telegram +``` + +The command line takes precedence over the config file and environment variables. When no channel is available, `bub gateway` prints `No channels are enabled` and exits. + +## Connect other platforms + +After installing a plugin, restart the gateway and use `bub hooks` to confirm the corresponding name appears under `provide_channels`. Channels without configured credentials are skipped. + +```bash +bub install bub-discord@main +``` + +| Platform | Channel name | Plugin | +| --- | --- | --- | +| Discord | `discord` | [`bub-discord`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-discord) | +| Slack | `slack` | [`bub-slack`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-slack) | +| WeChat | `wechat` | [`bub-wechat`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-wechat) | +| WeCom | `wecom` | [`bub-wecom`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-wecom) | +| Feishu | `feishu` | [`bub-feishu`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-feishu) | +| DingTalk | `dingtalk` | [`bub-dingtalk`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-dingtalk) | +| QQ | `qq` | [`bub-qq`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-qq) | + +For configuration, session rules, and access policies, see each plugin's README. See [bub-contrib](https://github.com/bubbuild/bub-contrib/tree/main/packages) and [hub.bub.build](https://hub.bub.build) for more packages. + +## Deploy the gateway + +Run it in the foreground for debugging, then install it as a system service once verified: + +```bash +bub gateway --install +bub gateway --uninstall +``` + +| System | Behavior | +| --- | --- | +| Linux | systemd user unit `bub-gateway.service`; to keep it running after logout, run `loginctl enable-linger $USER` | +| Windows | Scheduled task for the current user; starts at login and restarts on failure | +| macOS | `--install` is not supported; use launchd, tmux, or [container deployment](/docs/app/deploy/) | + +Installation records the current Python interpreter, workspace, and explicit `--enable-channel` values. Reinstall after changing the environment or workspace. + +## Custom channels {#custom-channels} + +When no existing plugin fits, implement your own channel. Subclass `Channel`, set `name`, and implement `start` / `stop`; implement `send` as well when replies need to go back to the platform: + +```python +import asyncio + +from bub.channels.base import Channel +from bub.channels.message import ChannelMessage + + +class MyChannel(Channel): + name = "myapp" # The name passed to --enable-channel + + def __init__(self, on_receive, token: str) -> None: + self._on_receive = on_receive + self._token = token + self._listener: asyncio.Task | None = None + + @property + def enabled(self) -> bool: + return bool(self._token) + + async def start(self, stop_event: asyncio.Event) -> None: + # Do not block: the gateway continues consuming messages only after start returns. + self._listener = asyncio.create_task(self._listen(stop_event)) + + async def stop(self) -> None: + if self._listener is not None: + self._listener.cancel() + + async def _listen(self, stop_event: asyncio.Event) -> None: + while not stop_event.is_set(): + chat_id, text = await self._next_message() # Your platform protocol + await self._on_receive( + ChannelMessage( + session_id=f"myapp:{chat_id}", + channel=self.name, + chat_id=chat_id, + content=text, + ) + ) + + async def send(self, message: ChannelMessage) -> None: + await self._post(message.chat_id, message.content) +``` + +Follow four contracts when implementing it: + +- Call the framework-provided `on_receive` for every inbound message; Turn output returns through `send`. +- Use `:` for `session_id` so sessions from different platforms do not collide. +- Return `False` from `enabled` when the configuration is incomplete; the gateway skips that channel. +- Return `True` from `needs_debounce` when consecutive short messages need to be merged. + +Expose the channel through a plugin's entry-point module with `provide_channels`: + +```python +import os + +from bub import hookimpl + + +@hookimpl +def provide_channels(message_handler): + return [MyChannel(on_receive=message_handler, token=os.environ.get("MYAPP_TOKEN", ""))] +``` + +After installing the plugin, run `bub gateway --enable-channel myapp`. See [Plugins](/docs/app/plugins/) for packaging and installation; for fields such as messages, media, and streaming output, see the [Python API](/docs/reference/python-api/#channels); for the rest of the channel interface, see the [Hooks reference](/docs/reference/hooks/). + +## Troubleshooting {#troubleshooting} + +| Symptom | Possible cause | Fix | +| --- | --- | --- | +| `No channels are enabled` | No usable channel | Check the token and the `.env` location; inspect plugins with `bub hooks` | +| No `telegram.start` in the log | Telegram token is empty | Set the token and confirm it reaches the `bub` process | +| Private chat receives `Access denied.` | The user allowlist rejected the user | Add the numeric ID or username to `BUB_TELEGRAM_ALLOW_USERS` | +| No response in a private chat | The chat allowlist rejected the chat | Add the chat ID to `BUB_TELEGRAM_ALLOW_CHATS` | +| No response in a group chat | Privacy Mode is on, or receive conditions are not met | Turn off Privacy Mode; for testing, @-mention the bot directly | +| Typing forever with no reply | The model did not call the `telegram` skill | See [reply rules](#telegram-outbound) | +| `409 Conflict` | Multiple long-polling processes use the same token | Stop the duplicate instances | +| No offline messages after a restart | Backlog updates are discarded at startup | Expected behavior | diff --git a/website/src/content/docs/docs/app/hooks.mdx b/website/src/content/docs/docs/app/hooks.mdx index 4a004329c..2e8bf9c1b 100644 --- a/website/src/content/docs/docs/app/hooks.mdx +++ b/website/src/content/docs/docs/app/hooks.mdx @@ -1,17 +1,30 @@ --- title: Hooks -description: Use hooks to shape prompts, control calls, save application state, and provide resources. +description: Use hooks to adjust prompts, intercept model and tool calls, save state, and provide application resources. --- -Choose a hook at the point where you want to change the Bub app. Keep the rest of the application in place: a prompt hook changes the input, a tool decision controls one call, and a store provider changes where conversation history lives. +Hooks are the Bub app's extension interface. You replace only the parts you need to change; the remaining stages keep using built-in implementations. -These examples belong in a [plugin](/docs/app/plugins/). Module-level functions can use a module entry point. For a class, register an instance or a factory that accepts the framework. +First choose a hook by task: -The [hooks reference](/docs/reference/hooks/) defines signatures, priority, and lifecycle rules. +| Goal | Hook | +| --- | --- | +| Rewrite model input | `build_prompt`, `system_prompt` | +| Modify or block a model or tool call before it happens | `before_llm_call`, `before_tool_call` | +| Measure completed calls | `after_llm_call`, `after_tool_call` | +| Save application state across Turns | `load_state`, `save_state` | +| Replace Tape storage or context | `provide_tape_store`, `build_tape_context` | +| Provide long-lived resources such as a database connection | `provide_lifespan` | +| Connect a new channel or background service | `provide_channels` | +| Add CLI commands | `register_cli_commands` | + +Signatures, return values, and dispatch rules are in the [Hooks reference](/docs/reference/hooks/); call order within a Turn is in [Turn](/docs/concepts/turn/#stages). + +The code below belongs in a [plugin](/docs/app/plugins/). Module-level functions use a module entry point; a class needs a registered instance, or a factory that receives `BubFramework`. ## Change the prompt {#change-the-prompt} -Use `build_prompt` to replace the incoming content: +`build_prompt` receives the inbound message, session ID, and state for the current turn, and returns the model input. Use `content_of()` to read plain text: ```python from bub import hookimpl @@ -20,12 +33,13 @@ from bub.envelope import content_of @hookimpl def build_prompt(message, session_id, state): - return f"Answer using the project's terminology.\n\n{content_of(message)}" + text = content_of(message) + return [f"Answer using the project's terminology.\n\n{text}"] ``` -This text-only example reads the envelope through `content_of`. For media, return the user content types described in the [reference](/docs/reference/hooks/#prompt-content). +The return value is `list[UserContent]`; elements can be strings or media objects such as `republic.image()`. Legacy formats such as plain strings are still converted, but new code should return a list. -To add a system instruction while keeping the incoming prompt, contribute a `system_prompt` fragment instead: +If you only want to append system instructions and keep the inbound content, use `system_prompt`: ```python from bub import hookimpl @@ -36,9 +50,13 @@ def system_prompt(prompt, state): return "Explain which files support your answer." ``` +`system_prompt` is an append-only hook; results from multiple plugins are joined with blank lines according to priority and do not replace each other. Keep each turn's system instructions stable to help model provider caching. + ## Control a model or tool call {#control-a-model-or-tool-call} -Use the agent interception hooks when you want to retain the builtin agent and change an individual call. For example, cap a request's output tokens: +Interception hooks run once, before the call happens. Exceptions they raise are logged and skipped, so to prevent execution you must return a decision. + +Cap output tokens: ```python from dataclasses import replace @@ -49,10 +67,11 @@ from bub.hooks.interception import LLMCallRequest @hookimpl def before_llm_call(request: LLMCallRequest, state): - return replace(request, max_tokens=min(request.max_tokens or 512, 512)) + max_tokens = min(request.max_tokens or 512, 512) + return replace(request, max_tokens=max_tokens) ``` -Return a decision to prevent a tool from running: +Deny a tool call: ```python from bub import hookimpl @@ -66,13 +85,19 @@ def before_tool_call(call: ToolCall, state): return None ``` -Your authorization logic sets `can_cancel_orders`; the hook uses that value to allow or deny the call. +The three `ToolCallDecision` outcomes: -An exception in these interception hooks is logged and skipped. Use `deny`, `replace`, or `LLMCallDecision.finish` for control. The [interception contract](/docs/reference/hooks/#interception-through-pluginagenthooks) explains when a decision stops the chain. +| Return value | Effect | +| --- | --- | +| `None` / `proceed()` | Continue execution; parameters may be replaced at the same time | +| `replace(result)` | Skip the tool and use the specified result | +| `deny(message)` | Skip the tool and return the message as a tool error | + +`replace` and `deny` stop later interception implementations. `LLMCallDecision.finish(text)` skips the model provider and uses the text directly as the result. See [Interception rules](/docs/reference/hooks/#interception-through-pluginagenthooks) for the full contract. ## Observe completed calls {#observe-completed-calls} -For call metrics, use the completion hooks: +Completion hooks run after a call finishes and suit logging, metrics, and auditing: ```python from bub import hookimpl @@ -80,50 +105,56 @@ from bub import hookimpl @hookimpl def after_llm_call(request, result, state): - print(f"model={request.model} duration_ms={result.duration_ms} error={result.error!r}") + print(f"model={request.model} error={result.error!r} elapsed={result.duration_ms}ms") @hookimpl def after_tool_call(call, result, state): - print(f"tool={call.tool} duration_ms={result.duration_ms} error={result.error!r}") + print(f"tool={call.tool} error={result.error!r} elapsed={result.duration_ms}ms") ``` -These hooks observe completed calls, including ordinary failures. They do not report cancellation. For per-chunk output, use the stream interface in your host or channel and follow its [closing rules](/docs/reference/python-api/#streams). +They cover successes and ordinary exceptions, but not cancellation or a consumer closing the stream early. To process text chunk by chunk, consume the stream returned by `agent.run()`; interception hooks do not provide streaming increments. -To replace the entire model stage, implement either `run_model` or `run_model_stream`. [Your first plugin](/docs/app/plugins/) shows a text replacement. A replacement owns its execution and recording behavior; the builtin agent's interception hooks run only when that agent is used. +To replace the entire model stage, implement one of `run_model` or `run_model_stream`. A replacement owns its own call, event, and recording behavior; the interception hooks above only fire when the built-in Agent is used. ## Keep application state across turns {#keep-application-state-across-turns} -The framework builds a fresh state dictionary for each inbound turn. Use `load_state` and `save_state` to carry your application's own values between turns: +The framework creates a new `TurnState` for each inbound message. `load_state` returns the initial fields, and `save_state` writes them back when the model stage ends: ```python +import asyncio + from bub import hookimpl class TurnCounter: def __init__(self) -> None: self.counts: dict[str, int] = {} + self.lock = asyncio.Lock() @hookimpl - def load_state(self, message, session_id): - return {"completed_turns": self.counts.get(session_id, 0)} + async def load_state(self, message, session_id): + async with self.lock: + return {"completed_turns": self.counts.get(session_id, 0)} @hookimpl - def save_state(self, session_id, state, message, model_output): - self.counts[session_id] = state["completed_turns"] + 1 + async def save_state(self, session_id, state, message, model_output): + async with self.lock: + self.counts[session_id] = state.get("completed_turns", 0) + 1 turn_counter = TurnCounter() ``` -This in-memory example counts model-stage exits, including failures. It resets when the process exits and assumes one turn at a time per session. Use durable storage and an appropriate concurrency strategy for production state. +This example only stores state in the process. Counts are lost after a restart and are not shared across processes; production environments should write to a database or another persistent store. + +`save_state` sits in the model stage's `finally`, so it is still called when the model fails or is cancelled. Failures in session resolution, state loading, or prompt construction never reach the model stage and therefore do not save. To distinguish success from failure, inspect `state` or the model output; do not assume a save means success. -`save_state` runs in the model stage's `finally` block, after session, state, and prompt preparation have succeeded. Errors during that preparation skip the save hook. Conversation history has a separate [Tape lifecycle](/docs/concepts/tape/#state). +Multiple `load_state` implementations are merged by priority, with higher-priority fields overriding same-named lower-priority fields; all `save_state` implementations run. Turn concurrency is decided by the host or channel policy, and there is no automatic session lock between `load_state` and `save_state`. -## Provide a tape store {#provide-a-tape-store} - +## Provide Tape storage and context {#provide-a-tape-store} -Return the store you want the application to use: +`provide_tape_store` selects the backend for conversation history: ```python from pathlib import Path @@ -137,11 +168,35 @@ def provide_tape_store(): return FileTapeStore(Path("./application-tapes")) ``` -This example selects a plain JSONL file store in the current directory. Bub's builtin application store adds application-specific search behavior; choose the store whose behavior you need. For a database-backed application store, see the [SQLAlchemy integration](/docs/integrations/storage/). +The framework calls the provider once when entering `framework.running()`. A backend that needs cleanup can return a synchronous or asynchronous generator, closing resources when the scope exits. Keep only one store provider; a higher-priority implementation overrides a lower-priority one. See the [SQLAlchemy integration](/docs/integrations/storage/) for a database backend example. + +```python +@hookimpl +def provide_tape_store(): + store = open_tape_store() + try: + yield store + finally: + store.close() +``` + +`build_tape_context` decides which Tape entries become model messages: -The framework resolves the provider once when `framework.running()` opens. A provider can yield a store and release it in `finally`. The [reference](/docs/reference/hooks/#runtime-resources) covers that lifecycle and the corresponding `provide_lifespan` hook. +```python +from dataclasses import replace + +from bub import hookimpl +from bub.core import default_tape_context -A plugin can also contribute a named sibling tape: + +@hookimpl +def build_tape_context(): + return replace(default_tape_context(), anchor="reviewed") +``` + +When replacing the context, keep the tool round-trip handling unless you truly need custom message types. Constructing a bare `TapeContext()` only rebuilds ordinary `message` entries. The `reviewed` anchor in the example must already exist on the Tape; reads raise `NOT_FOUND` when it is missing. + +A sidecar stores associated data in a companion Tape instead of the main context: ```python from bub import hookimpl @@ -156,11 +211,34 @@ def provide_tape_sidecar(): return ArtifactSidecar() ``` -The [shared Tape explanation](/docs/concepts/tape/) describes sidecar naming and merge behavior. +A sidecar with the same name keeps the higher-priority implementation. See [Tape sidecar](/docs/concepts/tape/#sidecars) for merge, reset, and failure behavior. + +## Manage application resources {#runtime-resources} + +`provide_lifespan` holds resources for the duration of the app run. Return a synchronous or asynchronous generator: + +```python +from bub import hookimpl + + +@hookimpl +async def provide_lifespan(): + client = await connect_database() # Provided by your module + try: + yield + finally: + await client.close() +``` + +Do not return an object produced by a `@contextmanager` / `@asynccontextmanager` decorator; the current runtime treats it as a plain value and does not enter the context. The generator itself is closed when the scope exits. + +The framework enters in the order `provide_lifespan` → `provide_tape_store` → `provide_steering_inbox`, and closes in reverse order on exit. Cleanup also runs when any stage raises. See [Runtime resources](/docs/reference/hooks/#runtime-resources) for resource and lifecycle details. -## Add an application command {#add-an-application-command} +`bub run`'s scope covers a single message; `bub chat` and `bub gateway` cover the entire app run; an embedded service should wrap its service lifecycle with `framework.running()`. The same Framework should not enter two overlapping scopes at once. -`register_cli_commands` receives the root Typer app: +## Add a CLI command {#add-an-application-command} + +`register_cli_commands` receives the root Typer app and must be implemented synchronously: ```python import typer @@ -175,6 +253,45 @@ def register_cli_commands(app: typer.Typer): typer.echo(f"hello, {name}") ``` -After installing the plugin, run `bub hello --name bub`. This hook must be synchronous. +After installing the plugin, you can run: + +```bash +bub hello --name bub +``` + +Plugins can also connect channels through `provide_channels`, replace the execution environment through `provide_environment`, and add configuration flows through `onboard_config`. See the [Hooks reference](/docs/reference/hooks/) for parameters and lifecycle. + +## Prompt for interactive input {#prompt-for-input} + +`register_cli_commands` and `onboard_config` are synchronous entry points. Use `bub.inquirer` to ask terminal users for input; do not create an event loop inside these hooks: + +```python +import typer + +from bub import hookimpl +from bub.inquirer import ask_confirm, ask_secret, ask_select, ask_text + + +@hookimpl +def register_cli_commands(app: typer.Typer): + @app.command() + def configure_orders() -> None: + endpoint = ask_text("Orders API URL") + environment = ask_select("Environment", ["development", "production"]) + token = ask_secret("API token") + if ask_confirm("Save this configuration?", kind="permission"): + save_orders_config(endpoint, environment, token) +``` + +| Function | Result and purpose | +| --- | --- | +| `ask_text(message, default="")` | Text input | +| `ask_secret(message)` | Hidden credential input | +| `ask_confirm(message, default=False, kind="question")` | Boolean confirmation; pass `kind="permission"` for permission prompts | +| `ask_select(message, choices, default="")` | Select one item from a list | +| `ask_fuzzy(message, choices, default=None)` | Fuzzy-search and select one item | +| `ask_checkbox(message, choices, enabled=None, validate=None)` | Select multiple items; the validator returns `True` or an error message | + +Ctrl+C, quitting a prompt, or an empty result raises `typer.Abort`. When called through the Bub CLI, these helpers publish `PromptChanged` as prompts open and close. Terminal status and external UIs can use it to display `question`, `permission`, or `auth` states. -For other application tasks, use `onboard_config` to contribute configuration, `provide_channels` to connect a transport, or `provide_environment` to choose where builtin tools run. Their inputs and return values are in the [hooks reference](/docs/reference/hooks/). +These helpers are for interactive CLIs. Gateways and HTTP requests have no terminal user; obtain input through configuration, channel messages, or the host's own UI instead. diff --git a/website/src/content/docs/docs/app/plugins.mdx b/website/src/content/docs/docs/app/plugins.mdx index 16269f3b6..01eb477c6 100644 --- a/website/src/content/docs/docs/app/plugins.mdx +++ b/website/src/content/docs/docs/app/plugins.mdx @@ -1,17 +1,15 @@ --- title: Plugins -description: Write a plugin, choose its entry point, and install it into the app. +description: Write, install, and distribute Bub plugins, and choose entry point, tool, and lifecycle forms. --- -A plugin changes how the Bub app handles a task. This page builds a small echo plugin so you can check discovery and turn processing without model credentials, then covers the entry-point choices, tool registration, sharing, and the WebAssembly route for other languages. +Plugins change Bub app behavior through hooks. A Python package declares a plugin object in the `bub` entry point group; Bub imports it and wires its hook implementations into the app. -A Python package declares an entry point in the `bub` group; Bub imports that target and registers its hook implementations. You can also reuse an existing plugin in your own service while keeping your host's entry point; see [Reusing plugins](/docs/sdk/reuse-plugins/). +The following first builds an echo plugin that needs no model credentials, so you can verify the complete create, install, and run flow. To reuse existing plugins, see [Reuse application plugins](/docs/sdk/reuse-plugins/). -## Create the package +## Create a plugin {#create-a-plugin} -Install Python 3.12+ and uv as described in [Install Bub](/docs/getting-started/install/). This tutorial creates a project environment containing Bub 0.6.0 and your plugin. - -Create this package layout: +Prepare Python 3.12+ and [uv](/docs/getting-started/install/), then create this project: ```text bub-echo-plugin/ @@ -22,7 +20,7 @@ bub-echo-plugin/ └── plugin.py ``` -Leave `__init__.py` empty. Put this in `pyproject.toml`: +`src/bub_echo_plugin/__init__.py` can stay empty. Declare the package and entry point in `pyproject.toml`: ```toml [build-system] @@ -33,7 +31,7 @@ build-backend = "hatchling.build" name = "bub-echo-plugin" version = "0.1.0" requires-python = ">=3.12" -dependencies = ["bub[app]>=0.6.0"] +dependencies = ["bub[app]"] [project.entry-points."bub"] echo = "bub_echo_plugin.plugin:echo_plugin" @@ -42,11 +40,7 @@ echo = "bub_echo_plugin.plugin:echo_plugin" packages = ["src/bub_echo_plugin"] ``` -The `bub` entry-point group tells the application where to find the plugin. - -## Implement the response - -Put this in `src/bub_echo_plugin/plugin.py`: +The entry point key `echo` is the plugin name; the value points to the object to load. Implement the model stage in `src/bub_echo_plugin/plugin.py`: ```python from bub import hookimpl @@ -61,116 +55,140 @@ class EchoPlugin: echo_plugin = EchoPlugin() ``` -The application still builds the incoming prompt. Your `run_model` implementation supplies the response instead of calling the builtin agent. +`run_model` replaces the built-in model stage and returns plain text; message preparation, state saving, and reply delivery are still handled by other hooks. -## Install and try it +## Install and run {#install-and-run} -From the plugin directory, install the project and its dependencies: +Sync the environment in the plugin project, then check the hook report: ```bash uv sync uv run bub hooks ``` -`uv sync` installs this package in editable mode. Run Bub through `uv run` in the same project so it can discover the plugin. To install into an existing activated application environment instead, use `uv pip install -e .` and run `bub hooks` there. - -The report should include `echo` under `run_model`. The builtin agent appears under `run_model_stream`. +`uv sync` installs the current project in editable mode. `bub hooks` should list `echo` under `run_model`, while the built-in Agent appears under `run_model_stream`. ```bash uv run bub run "hello from my plugin" ``` -With no other plugin overriding the model stage, the response contains: +When no other higher-priority model implementation exists, the output contains: ```text [echo:cli:local] hello from my plugin ``` -If `echo` is missing, check that the package was installed in the environment running `bub`, its entry-point group, and startup logs for an import failure. Changes to package metadata require reinstalling the package. If it is listed but another response wins, inspect the other model hooks and their priority. - -Bub registers builtin hooks before entry-point plugins. Hooks at equal priority generally run in reverse registration order; `tryfirst` and `trylast` affect that order. The [dispatch rules](/docs/reference/hooks/#priority-and-dispatch) explain which implementation runs first. +If `echo` does not appear, check in order: -## Choose an entry-point target +1. Whether the environment the plugin is installed into is the same one running `bub`; +2. Whether the group name and module path in `[project.entry-points."bub"]` are correct; +3. Whether startup logs recorded a plugin import failure. -The key `myplugin` identifies the plugin in diagnostics. The value names the Python object to import. Choose one target form: +Reinstall after changing entry point metadata. If `echo` appears but replies come from elsewhere, check other model hooks and their [priority](/docs/reference/hooks/#priority-and-dispatch). -| Target | Registration | Use it when | -| --- | --- | --- | -| `bub_myplugin.plugin` | Register the module | Hooks are module-level functions | -| `bub_myplugin.plugin:plugin` | Register the instance | Hooks share state on an object | -| `bub_myplugin.plugin:MyPlugin` | Call the class or factory with `BubFramework`, then register its result | Hooks need the framework | - -For the module form, define hooks at module level: - -```python -from bub import hookimpl +Bub registers built-in implementations first, then entry point plugins. At equal priority, implementations registered later run first; `tryfirst` and `trylast` can adjust the order. +## Choose an entry point form {#entry-point-forms} -@hookimpl -def build_prompt(message, session_id, state): - return "Describe this workspace." -``` +| Entry point target | Behavior | Use it when | +| --- | --- | --- | +| `bub_myplugin.plugin` | Registers the module | Hooks are module-level functions | +| `bub_myplugin.plugin:plugin` | Registers an existing instance | Hooks share object state | +| `bub_myplugin.plugin:MyPlugin` | Calls the class or factory and registers the return value | Hooks need access to `BubFramework` | -For the instance form, create the object in the target module: +When the entry point value is callable, Bub passes the current `BubFramework` to it: ```python -from bub import hookimpl +from bub import BubFramework, hookimpl class MyPlugin: + def __init__(self, framework: BubFramework) -> None: + self.framework = framework + @hookimpl def build_prompt(self, message, session_id, state): - return "Describe this workspace." + return f"Describe the workspace at {self.framework.workspace}." +``` +`build_prompt` replaces the model input. A real extension should choose the hook that matches its responsibility; see [Application hooks](/docs/app/hooks/). -plugin = MyPlugin() -``` +## Provide tools {#provide-tools} -Every callable target receives the framework, including classes. Accept it in the constructor or factory: +New plugins should explicitly provide `@tool` objects through `provide_tools`: ```python -from bub import BubFramework, hookimpl +from bub import hookimpl, tool +from bub.core.tools import Tool -class MyPlugin: - def __init__(self, framework: BubFramework) -> None: - self.framework = framework +@tool(name="orders.get") +def get_order(order_id: str) -> dict[str, str]: + """Look up an order by id.""" + return {"id": order_id, "status": "shipped"} + +class OrdersPlugin: @hookimpl - def build_prompt(self, message, session_id, state): - return f"Describe the workspace at {self.framework.workspace}." + def provide_tools(self) -> list[Tool]: + return [get_order] ``` -These examples replace the prompt. Choose the hook for your actual task from [Hooks](/docs/app/hooks/). +`BubAgent` takes a snapshot of the current tools when constructed. To add tools during a run, update that instance's `tools` dictionary; new Agents collect the tools again. + +Legacy plugins that rely on automatic `@tool` collection when their entry point loads, and do not implement `provide_tools`, still register their tools and produce a deprecation warning. New plugins should not use this fallback. See [Tools](/docs/app/tools/) for tool parameters, deferred loading, and code mode. -## Register tools during import +## Provide long-running services {#lifecycle} -If the package defines `@tool` functions, import their module from the entry-point module before an agent is created: +Components such as MCP clients and schedulers need to start with the process rather than in response to a message. Implement `Lifecycle` and provide it through `provide_channels` like any other channel: ```python -from . import tools # noqa: F401 +from bub.channels.base import Lifecycle + + +class MyRuntime(Lifecycle): + name = "myapp.lifecycle" + + async def start(self, stop_event) -> None: + await self._connect(stop_event) + + async def stop(self) -> None: + await self._close() ``` -The application takes a snapshot of registered tools when it creates a `BubAgent`. See [Tools](/docs/app/tools/) for registration, deferred loading, and code mode. +Lifecycle channels start and stop with the channel runtime and do not receive messages: + +- They appear among the channel names accepted by `--enable-channel`; +- When ordinary channels are listed explicitly, enabled `Lifecycle` channels are still attached automatically; +- Exclude one with `!name`, for example `--enable-channel '!mcp.lifecycle'`; +- `bub run` does not start channels, so it also does not start long-running services. + +For complete examples, see [`bub-mcp`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-mcp) and [`bub-schedule`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-schedule). + +## Share a plugin {#share-a-plugin} -## Share the plugin +A single distribution can provide a plugin, channels, and [skills](/docs/app/skills/#include-skills-in-a-wheel) at the same time: -Build and install a wheel into a separate environment before publishing it. Confirm that the entry point loads and that a representative task uses your hook. If you bundle skills or several plugins, follow [Distribution](/docs/app/distribution/). +```toml +[project.entry-points."bub"] +orders = "bub_orders.plugin" -Portable Agent Plugins use a different format: `plugin.json`, skills, and MCP servers. The [Agent Plugins integration](/docs/integrations/agent-plugins/) loads those packages through a Python plugin. +[tool.hatch.build.targets.wheel] +packages = ["src/bub_orders", "src/skills"] +``` -## WASM plugins (experimental) +For general wheel building, CLI packaging, and pre-release verification, see [Embedding and distribution](/docs/app/distribution/). Agent Plugins instead use `plugin.json`, skills, and MCP server definitions; see [Load Agent Plugins](/docs/integrations/agent-plugins/). -A plugin does not have to be Python. The [`bub-extism`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) plugin runs WebAssembly modules and maps their exports to hooks. Bub still dispatches the hooks and decides precedence; Extism only calls your module. +## WASM plugins (experimental) {#wasm-plugins} -Install it into the environment where Bub runs: +[`bub-extism`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) exposes WebAssembly module export functions as hooks through Extism, so plugins can also be written in other languages: ```bash bub install bub-extism@main bub hooks ``` -`extism.json` names each module and the hook it implements. `manifest` uses the Extism format, and `hooks` maps a Bub hook to a wasm export: +`extism.json` specifies the module files and hook mapping: ```json { @@ -189,6 +207,4 @@ export BUB_EXTISM_CONFIG_PATH=./extism.json bub run "Say hello from a prompt built outside Python." ``` -The [README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) lists the hooks this route supports and how arguments convert. Check both before you depend on it: not every hook is available, and every value crosses a Python-to-wasm boundary. The [examples](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism/examples) build one module in Go and one in Rust. - -Keep the builtin implementation for model calls and tape recording. A module suits hooks that do small work on their own. +Not every hook supports WASM, and arguments must cross the Python-to-WASM boundary. Before using it, check the [plugin README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) for the supported list and examples. diff --git a/website/src/content/docs/docs/app/skills.mdx b/website/src/content/docs/docs/app/skills.mdx index 396eadc5a..93eb58014 100644 --- a/website/src/content/docs/docs/app/skills.mdx +++ b/website/src/content/docs/docs/app/skills.mdx @@ -1,92 +1,94 @@ --- title: Skills -description: Write a reusable procedure, check that the app loads it, and package it for other users. +description: Write, verify, and distribute Bub skills, and understand how skills are discovered and loaded. --- -A skill gives the Bub app a procedure it can load for a task. This page walks through a project skill, then covers the file fields, discovery order, and packaging. Keep project-specific skills in the workspace, or include them in a Python package so other users can install them. +A skill keeps a set of instructions in `SKILL.md` for the model to use on demand. Project-specific skills can live in the workspace, while general skills can ship with a Python package. -Skill discovery is part of the application; a plain `core.Agent` uses the instructions you explicitly give it. +Skill discovery and loading are Bub app behavior. A plain `core.Agent` only uses the prompts you explicitly provide. -## Write the instructions +## Bundled skills {#builtin-skills} -Create a directory for the skill: +The Bub installation ships three skills; a skill with the same name in a project or user directory overrides them: -```bash -mkdir -p .agents/skills/repo-map -``` +| Skill | Purpose | External dependency | +| --- | --- | --- | +| `telegram` | Send, reply to, and edit Telegram messages | Telegram Bot token | +| `gh` | Manage repositories, issues, PRs, and Actions through the GitHub CLI | `gh` installed and authenticated | +| `skill-creator` | Guide the creation or update of skills | None | + +Run `bub run ",skill"` to see the full list actually discovered in the current workspace. Bundled skills are listed too, which does not mean the corresponding external service has been configured. + +## Create a project skill {#write-a-skill} -Create `.agents/skills/repo-map/SKILL.md`: +Create `.agents/skills/repo-map/SKILL.md` in the workspace: ```markdown --- name: repo-map -description: Read the local repository layout before changing code. +description: Read the local repository structure before changing code. --- -1. Run `rg --files` to see the repository layout. -2. Read the files that explain the requested change. +1. Run `rg --files` to see the repository structure. +2. Read the files that explain this change. 3. Describe the affected modules before editing code. ``` -The `name` must match the directory name. Use lowercase words separated by hyphens. The description helps the model decide when to use the skill. +`name` must match the directory name. `description` helps the model decide when to use the skill, so state the task and when it applies. -## Check that Bub can load it +## Verify a skill {#verify-a-skill} + +Run this in the workspace: ```bash bub run ",skill name=repo-map" ``` -The command prints the skill's instructions without calling a model. If it returns `(no such skill)`, check the path and the YAML frontmatter. - -## Use it in a conversation +The command prints the skill body directly without calling the model. If it returns `(no such skill)`, check the file path, directory name, and YAML frontmatter. -With your model configured, run: +Reference it explicitly in a model session: ```bash bub run 'Use the $repo-map skill, then list the top-level modules you found.' ``` -`$repo-map` includes the skill's instructions in this turn's system prompt. The model then follows them using the tools available in the application. Install `rg` in the execution environment for this example. - -## Skill file fields - -Each skill directory contains a `SKILL.md` with YAML frontmatter: +`$repo-map` expands the skill body into that Turn's system prompt. The model then calls the tools provided by the app to carry out the steps; this example requires `rg` to be installed in the execution environment. -```markdown ---- -name: my-skill -description: Describe the task this skill helps the agent complete. ---- +## SKILL.md fields {#skill-fields} -Write the procedure here. -``` +| Field | Requirement | +| --- | --- | +| `name` | Required; matches the directory name; lowercase letters or digits separated by hyphens; at most 64 characters | +| `description` | Required; non-empty string; at most 1,024 characters | +| `metadata` | Optional mapping; keys and values must be strings | -Bub checks these fields when discovering skills: +Supporting files can live in directories such as `scripts/`, `references/`, and `assets/`. The skill body supports: -| Field | Requirement | +| Variable | Expansion | | --- | --- | -| `name` | Matches the directory name; lowercase words separated by hyphens; at most 64 characters | -| `description` | Nonempty string, at most 1,024 characters | -| `metadata` | Optional map with string keys and string values | +| `$SKILL_DIR` | Absolute path of the skill directory | +| `$PYTHON` | Path of the Python interpreter running Bub | +| `${config.path.to.value}` | Value of the corresponding field in the Bub configuration | -Add `scripts/`, `references/`, or `assets/` alongside `SKILL.md` when the procedure needs supporting files. In the body, `$SKILL_DIR` expands to the skill's directory and `$PYTHON` to the interpreter running Bub. Application configuration can be inserted with `${config.path.to.value}`. +Scripts in a skill do not run automatically just because the files exist. The model still needs to call them through a tool, so also provide instructions for using the script and the tools it requires. -## Know which skill is selected +## Discovery order and loading {#discovery} -By default, the application searches these roots in order. The first matching name wins: +By default, the app searches in the following order, and the first match wins when names collide: 1. `/.agents/skills/` 2. `~/.agents/skills/` 3. Installed `skills` namespace packages, including Bub's bundled skills -A project skill can therefore replace a packaged skill with the same name. `BubAgent(skill_dirs=...)` replaces the default roots with the directories you supply. +A project skill can therefore override a packaged skill with the same name. The legacy project directory `.agent/skills/` is still read and produces a migration warning; use `.agents/skills/` for new skills. + +`BubAgent(skill_dirs=...)` replaces the default search locations with the set of directories you supply, and their order is the priority; pass an empty collection to disable skill discovery. -The system prompt lists available skills. A `$skill-name` hint expands that skill for the current turn; `,skill name=skill-name` prints its body through the command path. +Every model Step's system prompt lists the names and descriptions of available skills. The body of a skill is expanded for the current Turn only when the prompt contains `$skill-name`. `,skill name=skill-name` is a separate command invocation that only prints the body. -## Include skills in a wheel - +## Distribute with a Python package {#include-skills-in-a-wheel} -Place a `skills` namespace package alongside your Python code: +Add a `skills` namespace package under `src/`: ```text src/ @@ -107,18 +109,18 @@ With Hatch, include both directories in the wheel: packages = ["src/bub_myplugin", "src/skills"] ``` -Keep `skills/` as a namespace package without an `__init__.py` so multiple installed packages can contribute to it. Other build backends must produce the same installed layout. +Do not add an `__init__.py` inside `skills/`. Namespace packages let multiple distributions contribute skills together; other build tools should also produce the same installed directory layout. -Skills can ship with a plugin or an application distribution. Bub discovers them through the installed `skills` namespace package. +Skills can ship with a plugin or be packaged independently; they are not required to implement hooks at the same time. -## Check the installed package +## Check the installation {#verify-installation} -Install the built wheel into the environment running Bub, then ask it to load the skill: +Install the built wheel into the same environment that runs Bub, then run: ```bash bub run ",skill name=my-skill" ``` -If it returns `(no such skill)`, check the directory name, frontmatter, and wheel contents. If it prints an unexpected version, look for a project or user skill with the same name. +If the skill cannot be found, check whether the wheel contains `skills/my-skill/SKILL.md`. If another version is printed, check whether a project or user directory has a skill with the same name, since those have higher priority. -To distribute a CLI and plugins alongside these skills, see [Distribution](/docs/app/distribution/). +For general wheel building, CLI packaging, and complete release verification, see [Embedding and distribution](/docs/app/distribution/). diff --git a/website/src/content/docs/docs/app/subagents.mdx b/website/src/content/docs/docs/app/subagents.mdx new file mode 100644 index 000000000..017a60aab --- /dev/null +++ b/website/src/content/docs/docs/app/subagents.mdx @@ -0,0 +1,99 @@ +--- +title: Subagents +description: Delegate tasks with the builtin subagent tool, choose a session, and restrict the model, tools, and skills available to it. +--- + +The Bub app includes a `subagent` tool that lets the current agent delegate a task to another agent run. The child uses the same `BubAgent`, model configuration, tools, and execution environment, but can have its own session, model, and available capabilities. + +Use it to keep research, reviews, and other relatively independent subtasks out of the main conversation. It is not a security sandbox or a standalone multi-agent workflow system. + +## Delegate a task {#delegate} + +Start `bub chat` and ask for a subagent explicitly: + +```text +Use the subagent tool to review the authentication flow. +Give it a temporary session and allow only fs.read and bash. +Do not modify files. Return findings with file paths. +``` + +The parent calls `subagent`. The child completes its own model-and-tool loop, then returns text to the parent. The tool call and the nested run are visible in the terminal and traces. + +To let the model decide when to delegate, describe the policy in a system prompt or skill—for example, “give code review to a read-only subagent before the parent edits the code.” + +## Choose a session {#sessions} + +The `session` parameter selects the child session: + +| Value | Behavior | Use it for | +| --- | --- | --- | +| `temp` (default) | Creates a `temp/` session; new tape entries are not merged back | One-off research and separate context | +| `inherit` | Uses the current `session_id` | Keeping the subtask in the same conversation history | +| Any other string | Uses that session ID | Continuing or inspecting the child session later | + +A temporary session returns only the final `output` and `errors` to the parent; it does not retain the child's intermediate conversation. With `inherit`, parent and child runs may write to the same session. Avoid running them concurrently when you need a clear history, or use a separate named session. + +The workspace and session ID together determine the tape name. Named sessions and `temp/` follow the same merge rules as direct `BubAgent.run_stream()` calls. + +## Restrict the model, tools, and skills {#parameters} + +Use `prompt` to state the subtask, expected result, and constraints. Set the other parameters when you need separate context, a different model, or a narrower set of tools and skills. See the [built-in tools reference](/docs/reference/tools/#subagent) for the complete parameters, types, and defaults. + +Tool names accept runtime names or model aliases, such as `fs.read` and `fs_read`. Unknown names raise an error. Skill names are case-insensitive. + +A read-only review could use these parameters: + +```json +{ + "prompt": "Review the authentication flow. Do not modify files.", + "session": "temp", + "allowed_tools": ["fs.read", "bash"], + "allowed_skills": [] +} +``` + +This JSON illustrates the tool parameters; it is not a command you need to paste. `allowed_skills: []` disables skill discovery. When minimizing tool access, provide an explicit, non-empty `allowed_tools` list. + +`allowed_tools` really filters the child run, but **the model still generates the parameter in chat**. When permissions are a security boundary, also enforce them through `BubAgent.run_stream()` or `before_tool_call`; do not rely on natural-language instructions alone. + +## Shared state and isolation {#boundaries} + +| Resource | Relationship | +| --- | --- | +| `BubAgent`, plugin hooks, model credentials | Shared | +| Workspace, `Environment`, files, external systems | Shared | +| Turn state | Shallow-copied; nested mutable objects may still be shared | +| Tools and skills | Restricted by parameters | +| Tape | Determined by `session` | +| Final text and errors | Returned to the parent | + +A temporary session separates **conversation records and model context**, not side effects. File changes, network requests, and processes produced by the child are not rolled back when it uses `temp`. Use a separate [execution environment](/docs/app/tools/#environment) for execution isolation, and [tool interception hooks](/docs/app/hooks/#control-a-model-or-tool-call) for approval policies. + +The tool returns: + +```python +{ + "session_id": "temp/1a2b3c4d", + "output": "...", + "errors": [], +} +``` + +`output` contains text deltas only. Other stream events remain available through events and tracing. The child's trace sits below the tool span that started it; see [Observability](/docs/integrations/observability/). + +## When to use it {#when-to-use} + +Good fits: + +- Keep large search or reading tasks in temporary context and return only conclusions. +- Separate read-only review from later edits. +- Choose another model for one subtask. +- Give a subtask a smaller set of tools and skills. + +It does not replace: + +- Permission approval or a security sandbox. +- Cross-process queues and distributed locks. +- Automatic role orchestration, planner loops, or long-term memory. + +You can build those requirements on Bub's tools, events, tapes, and channels, but the host must define their lifecycle and consistency rules. diff --git a/website/src/content/docs/docs/app/terminal.mdx b/website/src/content/docs/docs/app/terminal.mdx index 764221f00..637950146 100644 --- a/website/src/content/docs/docs/app/terminal.mdx +++ b/website/src/content/docs/docs/app/terminal.mdx @@ -1,44 +1,123 @@ --- title: Terminal -description: Continue a conversation with bub chat or run one task from the shell with bub run. +description: Interactive sessions, steering a running agent, commands, and background processes in Bub. --- -The Bub app has two terminal entry points: `bub chat` for a conversation and `bub run` for one message. [Configure a model](/docs/app/configure/) before calling it. +import { Aside } from '@astrojs/starlight/components'; -## Continue a conversation +`bub run` handles a single message, while `bub chat` keeps a session open and is better suited to continuous work. Both use the same model, tools, and Tape. + +## Start an interactive session ```bash bub chat ``` -Each input submits a message. Press Ctrl-D or enter `,quit` or `,exit` to leave. The default session ID is `cli_session`. - -The toolbar shows the speed of the latest model call, measured from the first streamed chunk to the end of the response. It counts every output token, reasoning included, and excludes both the time before the first chunk and tool execution. The `ttft` next to it is that excluded time to first chunk. When the provider does not report usage, it shows `speed:- tok/s`. +Type your message and press Enter to send it. `,help` lists the internal commands currently available, and Ctrl+D, `,quit`, or `,exit` exits. Input history is saved per workspace in the `history/` directory under the Bub home, separately from the session's Tape. -## Run a task from the shell +You can also submit the first message directly at startup: ```bash -bub run "summarize CHANGELOG.md" +bub chat "Inspect this repository and suggest a first task." ``` -The command prints a channel label and the response. Its default session ID is `cli:local`. To share history with an interactive conversation, supply the same ID: +## Steer a running agent {#steering} + +**Steering** adds an instruction while the current Turn is still running so the agent can adjust the task in progress. You don't have to wait for the current generation to finish; just type the correction and press Enter: + +```text +Do not modify files yet. List the problems and suggestions first. +``` + +The CLI hands the message to the steering inbox (`SteeringInbox`). The agent reads it when building a model request in a later Step; a model or tool call already in progress is not forcibly interrupted. + +Steering and queueing differ in when the message takes effect: + +| Approach | Handling | Use it for | +| --- | --- | --- | +| Steering | Injects into later model requests in the current Turn; messages that cannot be consumed in time become pending messages | Correcting direction, adding constraints, and providing new information relevant to the current task | +| Queue up | Processed as a follow-up task after the current Turn ends | Adding another task or running several requests in order | + +The current CLI steers ordinary input by default while a run is in progress; there is no separate queueing shortcut. Steering messages that the current Turn does not consume automatically enter the pending queue instead of being dropped. + +To stop the task in the current session, use `,quit`; it clears pending messages and cancels running tasks. + +## View the reasoning process + +Enter `,thinking` to toggle between expanded and collapsed: + +```text +,thinking +``` + +It only controls whether the terminal displays the `reasoning` deltas returned by the model; it does not change the model or the reasoning effort. To adjust reasoning effort, use: + +```text +,reasoning_effort high +``` + +The available values depend on the model provider. The setting takes effect from the next Turn and is saved in the current session's Tape. + +## Choose a session + +`bub run` uses `cli:local` by default, and `bub chat` uses `cli_session`. Specify the same session ID explicitly to continue history between the two entry points: ```bash -bub chat --session-id cli:review -bub run --session-id cli:review "Summarize our decisions." +bub chat --session-id cli:echo +bub run --session-id cli:echo "Summarize what we discussed." ``` -Use the same workspace and storage configuration to read the same conversation history. +The Tape name also includes the workspace path, so the same session ID does not share history across different workspaces. -## Run a command directly +## Use commands {#commands} -The default command prefix is a comma. Commands call tools or the shell without a model request: +The command prefix defaults to `,` and is used to skip the model call and execute a tool directly: ```bash bub run ",help" bub run ",fs.read path=README.md" ``` -You can enter these commands in an interactive conversation too. Ctrl-X toggles shell mode, which adds the command prefix to your input. +You can enter commands directly in an interactive session too. Press Ctrl-X to switch between Agent and Shell input modes; Shell mode automatically adds the command prefix to your input. + +Common commands: + +| Command | Purpose | +| --- | --- | +| `,model openai:gpt-5-mini` | Switch the model for the current session | +| `,reasoning_effort high` | Set the reasoning effort for the current session | +| `,code_mode enable=true` | Enable code mode | +| `,tape.info` | View entries, anchors, and token usage | +| `,tape.search query=error` | Search the current session record | +| `,tape.anchors` | List context boundaries | +| `,tape.handoff name=reviewed summary='...'` | Move the default context start point and save a summary | +| `,tape.reset archive=true` | Archive the current Tape and start over | +| `,thinking` | Expand or collapse reasoning output | + +`,tape.reset archive=true` backs up the main Tape and its sidecar to `/tapes/`, with a UTC timestamp and a `.bak` suffix in the file names. Using `archive=false` or plain `,tape.reset` skips the backup. After a reset, a new `session/start` anchor is created, and the old files are not restored automatically. + +`handoff` does not generate or inject a summary automatically. `summary` is stored in the anchor's `state`; whether it is read later depends on the prompt or context policy. See [Tape](/docs/concepts/tape/#anchors) for details. + + + +## Run background processes {#background-shell} + +In `bub chat`, use `background=true` to start long-running processes such as a development server: + +```text +,bash command='npm run dev' background=true +``` + +The result includes a `shell_id`. Use it to read output incrementally or terminate the process: + +```text +,bash.output shell_id=bsh-12345678 +,bash.output shell_id=bsh-12345678 offset=1200 limit=4000 +,bash.kill shell_id=bsh-12345678 +``` + +`offset` is the character position in the buffered output, not a line number; use `next_offset` from the returned value for the next read. `limit` caps the number of characters returned in this call. -The CLI owns the foreground terminal, so the gateway excludes it by default. See [Channels](/docs/app/channels/) for gateway selection and batching, or the [CLI](/docs/reference/cli/) for every option. +A foreground `bash` call waits 30 seconds by default. On timeout, the process is not treated as failed; it moves to the background and returns a `shell_id`. Adjust this with `timeout_seconds`. Background tasks should be managed within the same `bub chat` or gateway process, and they are cleaned up when the application exits. diff --git a/website/src/content/docs/docs/app/tools.mdx b/website/src/content/docs/docs/app/tools.mdx index 70792e531..4b9a9c67c 100644 --- a/website/src/content/docs/docs/app/tools.mdx +++ b/website/src/content/docs/docs/app/tools.mdx @@ -1,52 +1,51 @@ --- title: Tools -description: Register tools for BubAgent and choose how the app makes them available. +description: Discover and use tools in the Bub app, limit what is available, and configure deferred loading and code mode. --- -Give the Bub app an action it can perform for your work. The [Tools and CodeMode](/docs/sdk/tools/) page explains function schemas, results, and tool context. This page covers application registration and the policies applied by `BubAgent`. +Tools perform concrete actions for the Bub app. The app already bundles tools for files, Shell, Web, Tape, and subagents, and you can add your own through plugins. Function signatures, result rendering, and `ToolContext` are covered in [Custom tools and code mode](/docs/sdk/tools/); for the parameters and defaults of built-in tools, see the [built-in tools reference](/docs/reference/tools/). -## Register a tool +## Built-in tool catalogue {#builtin-tools} -Define the tool in your plugin's `tools.py`: +The model can choose tools based on the task, and you can also call them directly with a command prefix. Names the model sees replace dots with underscores; for example, `fs.read` appears as `fs_read`. -```python -from bub import tool - - -@tool(name="orders.get") -def get_order(order_id: str) -> dict[str, str]: - """Look up an order by id.""" - return {"id": order_id, "status": "shipped"} -``` +| Task | Tool | Description | +| --- | --- | --- | +| Run processes | `bash` | Executes Shell; supports a working directory, timeouts, and background runs | +| Manage background processes | `bash.output`, `bash.kill` | Read output incrementally and terminate processes | +| Read and write files | `fs.read`, `fs.write`, `fs.edit` | Paged reads, whole-file writes, and text replacement | +| Fetch web pages | `web.fetch` | Issues GET requests, preferring Markdown; use a search plugin for web search | +| Use skills | `skill` | List skills or read the body of a specified skill | +| Discover deferred tools | `tool.describe` | Loads a tool definition so it can be called from the next Step | +| Inspect the session | `tape.info`, `tape.search`, `tape.anchors` | View usage, search records, and list anchors | +| Manage context | `tape.handoff`, `tape.reset` | Move the context start point; optionally archive before resetting | +| Delegate tasks | [`subagent`](/docs/app/subagents/) | Run a subagent with an isolated or specified session | +| Execute composed logic | `run_code` | Compose tool calls in Python in code mode | +| Read large results | `spill.read` | Read large tool results moved out of the main context in chunks | -The decorator only creates a `Tool`. Provide it from your [plugin](/docs/app/plugins/) with the `provide_tools` hook: +`model`, `reasoning_effort`, `code_mode`, `help`, and `quit` are available only as commands and are not exposed directly to the model. See [Terminal](/docs/app/terminal/#commands) for command usage and [Background processes](/docs/app/terminal/#background-shell) for a background example. -```python -from bub import hookimpl -from bub.core.tools import Tool +See [Provide tools](/docs/app/plugins/#provide-tools) for creating custom tools, registering them with `provide_tools`, and loading plugins. Once registered, use the policies below to limit the current Turn, defer definitions, or choose the execution environment. -from .tools import get_order +## Limit the tools for a Turn {#allowed-tools} +Pass `allowed_tools` when calling `BubAgent.run_stream()`: -class OrdersPlugin: - @hookimpl - def provide_tools(self) -> list[Tool]: - return [get_order] +```python +stream = await agent.run_stream( + session_id="customer-42", + prompt=["Look up order A123"], + allowed_tools={"orders.get"}, +) ``` -`BubAgent(tools=None)` collects these tools when constructed. For a duplicate name, the tool of the higher-priority plugin wins. To add tools later, update that agent's `tools` dictionary explicitly. - -Run `bub hooks` to check that your plugin implements `provide_tools`. The model-facing name of `orders.get` is `orders_get`. +It accepts runtime names or model aliases. `None` allows all instance tools, an empty collection allows none, and an unknown name raises `ValueError`. -## Choose which tools a turn can use +The filter applies to tools the model calls directly and to code mode. Comma commands use the Agent instance's tools directly and are not limited by this list; when you also need to constrain commands, use a [tool-call decision](/docs/app/hooks/#control-a-model-or-tool-call). -When calling `BubAgent.run_stream`, pass `allowed_tools` to limit the model's tools for that turn. It accepts runtime names or model-facing names. `None` allows all instance tools; an empty collection allows none. Unknown names raise `ValueError`. +## Load tool definitions on demand {#deferred-tools} -This filter applies to model and code-mode tools. Comma commands use the agent's instance tools directly. Use [tool-call decisions](/docs/app/hooks/#control-a-model-or-tool-call) when a rule must also govern command execution. - -## Load tools when needed - -Mark a tool `deferred=True` to keep its full definition out of the initial model request: +When there are many tools, set `deferred=True` to avoid putting every full definition into the initial model request: ```python from bub import tool @@ -58,45 +57,47 @@ def get_order(order_id: str) -> dict[str, str]: return {"id": order_id, "status": "shipped"} ``` -`BubAgent` lists allowed deferred tools in the system prompt and supplies `tool_describe` when at least one remains. The model calls `tool_describe` to load a definition. The application records `tool.loaded` entries, so loaded tools remain available until the tape is reset: the model can call a tool in the steps after loading it, and later turns offer it directly. Asking `tool_describe` for a tool that is already loaded returns its name under `already_loaded` instead of the definition. +`BubAgent` lists the names of deferred tools in the system prompt and supplies `tool.describe`. The model calls it to load a definition, and can call the tool directly from the next Step onward. + +Loads are recorded as `tool.loaded` events on the Tape, so later Steps and Turns can keep using the tool until the Tape is reset. `allowed_tools` limits both how tools are listed and how they are loaded. -The turn's allowlist controls both listing and loading. In code mode, allowed `auto` and `code` tools are available to Python immediately; deferral applies to the tools still offered directly to the model. +In code mode, allowed `auto` and `code` tools can be called directly from Python; deferred loading only affects tools still offered directly to the model. -## Use code mode in the app +## Use code mode {#code-mode} -Enable code mode for the current session: +Enable code mode in a session: ```text ,code_mode enable=true ``` -The change applies on the next turn and is recorded for recovery after a restart. Use `,code_mode enable=false` to switch back. When calling `BubAgent` directly with your own state, set `state["code_mode"] = True` for that run. +The setting takes effect from the next Turn and is recorded in the Tape so it can be restored after a restart. Use `,code_mode enable=false` to turn it off. When calling `BubAgent` directly, set `state["code_mode"] = True` for that run. -When `run_code` is allowed, the app selects tools according to `exposure`: +When `run_code` is available, the app selects tools according to `exposure`: | `exposure` | Code mode off | Code mode on | | --- | --- | --- | -| `auto` | Offered directly to the model | Available through `run_code` | -| `direct` | Offered directly to the model | Offered directly; unavailable under `tools.*` | -| `code` | Unavailable to the model | Available through `run_code` | +| `auto` | Offered directly to the model | Called through `run_code` | +| `direct` | Offered directly to the model | Still offered directly; not available under `tools.*` | +| `code` | Unavailable to the model | Called through `run_code` | | `command` | Comma command only | Comma command only | -If `run_code` is excluded, the turn uses the code-mode-off selection. The app writes a Python tool stub under `/codemode/` and includes its path in the system prompt. Python calls such as `await tools.orders_get(order_id="123")` use the selected tools and pass through the application's interception hooks. +When `run_code` is not in the allowed tool set, the Turn follows the code-mode-off rules. -The app connects CodeMode to the session's environment and model settings. Code can use the Republic bridge and media attachment tools described in [CodeMode](/docs/sdk/tools/#compose-tools-with-codemode). Model credentials remain on the host when using the bridge; the default local execution environment still runs code on that host. +The app writes Python tool declarations under `/codemode/` and adds the file path to the system prompt. Code calls tools with `await tools.orders_get(order_id="123")`, still passing through the app's interception hooks. -`BubAgent` interprets `state["code_mode"]` and the `deferred` flag to apply these selection rules. With a plain `core.Agent`, [compose CodeMode explicitly](/docs/sdk/tools/#compose-tools-with-codemode) and supply your own deferred-loading policy if you need one. +Code mode uses the session's execution environment and model configuration. The Republic bridge handles model credentials on the host side; the default `LocalEnvironment` still runs generated code with the host user's privileges and is not a security sandbox. A core Agent must [compose CodeMode explicitly](/docs/sdk/tools/#compose-tools-with-codemode). -## Choose the execution environment +## Choose the execution environment {#environment} -Builtin `bash`, `bash.output`, `bash.kill`, `fs.*`, and `run_code` use the session's `Environment`. The default is `LocalEnvironment`, which runs on the host. +The built-in `bash`, `bash.output`, `bash.kill`, `fs.*`, and `run_code` use the session's `Environment`. The default is `LocalEnvironment`, which runs on the host. -Provide another environment through a plugin's `provide_environment(session_id, workspace)` hook. The framework caches one environment per session and closes it when `framework.running()` exits. [Tools and CodeMode](/docs/sdk/tools/#choose-an-execution-environment) describes the environment interface. +Plugins provide other environments through `provide_environment(session_id, workspace)`. The framework caches one environment per session and closes it when `framework.running()` exits. See [Execution environment](/docs/sdk/tools/#choose-an-execution-environment) for the interface. -A custom application tool can access that environment with `environment_from_state(context.state)` from `bub.builtin.environment`. The Python tool runs on the host and explicitly delegates operations to the environment when needed. +Custom tools obtain the environment with `bub.builtin.environment.environment_from_state(context.state)`. Ordinary Python tool functions still run in the host process; only operations that explicitly call the environment enter it. -## Add instructions alongside the tool +## Add instructions alongside a tool {#instructions} -Put workspace-wide instructions in `AGENTS.md`. Use [skills](/docs/app/skills/) for procedures the model can load for a task. A skill can tell the model when to call your tool; the Python function defines what that tool does. +Put workspace-wide conventions in `AGENTS.md`, and procedures loaded per task in [skills](/docs/app/skills/). A skill can tell the model when to call a tool; the Python function performs the actual work. -Comma commands let a person invoke registered tools without asking the model. See [text commands](/docs/sdk/tools/#call-a-tool-without-a-model) for command syntax. +Users can also call tools directly with a command prefix without going through the model. See [Text commands](/docs/sdk/tools/#call-a-tool-without-a-model) for the syntax. diff --git a/website/src/content/docs/docs/app/workspace.mdx b/website/src/content/docs/docs/app/workspace.mdx index 3e8928cf8..0f41abde8 100644 --- a/website/src/content/docs/docs/app/workspace.mdx +++ b/website/src/content/docs/docs/app/workspace.mdx @@ -1,39 +1,41 @@ --- title: Workspace -description: Create a workspace, add your instructions, and check that commands work. +description: How to use Bub within a workspace. --- -Bub works in a directory: the workspace. It reads and writes files there, and it takes the project instructions from there. +import { Aside } from '@astrojs/starlight/components'; -This page creates a workspace, adds your instructions, and checks that the app's commands run. The check does not need model credentials. +By default, Bub runs in a workspace, which is simply a directory. It reads information and instructions about the current workspace from there. -## Create a workspace +## Prepare a workspace (optional) -Make a directory for your project, and run the remaining commands in it: +If you don't have a project yet and are just getting started with Bub, create a directory first: ```bash mkdir hello-bub cd hello-bub ``` -Put your working instructions in `AGENTS.md`: +Then create an `AGENTS.md` file in that directory and write the following content into it: -```bash -cat > AGENTS.md <<'EOF' +``` You are the Bub agent for this workspace. -Read files before editing them. +Read the relevant content before editing files. Keep replies short and specific. -EOF ``` -The application reads this file when it builds the system prompt. Change it as you learn which instructions help with your work. +From then on, each time Bub runs, it reads these rules from the instructions so the model can follow them in its output. After trying the example, keep editing this file and see how the model's output changes under different rules. + + -## Check a command +## Check commands + +Now try listing the commands available in Bub from the terminal in a workspace: ```bash bub run ",help" ``` -The output lists commands such as `,skill` and `,tape.info`. This checks the command path of the app without a model call. - -Commands begin with a comma at the start of a line. An unknown command falls back to a shell command, so use a known command for this check. +From then on, any input that starts with a `,` comma calls the related command. You can change it to any character you like in the [settings](/docs/reference/settings/#agent-settings). diff --git a/website/src/content/docs/docs/concepts/events.mdx b/website/src/content/docs/docs/concepts/events.mdx index 39fa8b071..f193484ec 100644 --- a/website/src/content/docs/docs/concepts/events.mdx +++ b/website/src/content/docs/docs/concepts/events.mdx @@ -1,46 +1,25 @@ --- title: Events -description: Watch a run, change or stop a model or tool call, and record the events you need. +description: Use the EventBus to observe and control Agent runs, and tell stream events apart from Tape records. --- -import { Aside } from '@astrojs/starlight/components'; +Every Agent has an event bus (`EventBus`). Register handlers through `agent.events` to record a run, or to make a decision before a model or tool call. -Agent events let you see a run and let you decide before a model or tool call happens. Every agent creates an `EventBus` and registers a `TapeRecorder`. You add handlers through `agent.events`. +## Telling the three kinds of data apart {#three-kinds} -Events are the only cross-cutting seam in Bub. Logging, permissions, budgets, auditing, and UI progress can all be written as handlers alone, without touching the loop or the tools. - -## Three kinds of information {#three-kinds} - -"Event" means three different things in Bub, and mixing them up is the easiest way to write a bug. Separate them first: - -| Type | Purpose | How to read it | Who reads it | -| --- | --- | --- | --- | -| `AgentEvent` | Run lifecycle, and call interception. | `agent.events.on(...)` | host code and plugins | -| `StreamEvent` | Output for the caller, such as text and tool progress. | iterate the stream from `agent.run()` | code that calls the agent | -| `TapeEntry` | The stored conversation and some run records. | query the [tape](/docs/concepts/tape/) | the model, on later runs | - -How the three relate: - -```mermaid -flowchart LR - loop["agent loop"] - loop -->|"emit"| bus["EventBus"] - bus --> rec["TapeRecorder
(builtin, registered first)"] - bus --> yours["your handlers"] - loop -->|"yield"| stream["StreamEvent stream"] - rec --> tape[("tape entries")] - stream --> caller["the caller"] - yours -->|"set decision / write tape"| loop - tape -->|"next run reads context"| loop -``` - -Mind the direction of the arrows. `StreamEvent` is a **one-way exit**: you can consume it, but you cannot change the run through it. To change the run, use an `AgentEvent` handler. Conversely, a `TapeEntry` is **after the fact**; writing one does not affect the step in progress. +| Type | Purpose | How to access it | +| --- | --- | --- | +| `AgentEvent` | Observe the lifecycle and control model and tool calls | `agent.events.on(...)` | +| `StreamEvent` | Deliver text, tool progress, usage, and errors to the caller | Iterate the stream returned by `agent.run()` | +| `TapeEntry` | Save the conversation and some run records | Query the [Tape](/docs/concepts/tape/) | -The loop controls execution, calls event handlers at fixed points, and only then moves on. TapeRecorder saves selected round-trips and lifecycle records. For anything else you want kept, write your own handler. +The three kinds of data flow in different directions: -## Handlers {#handlers} +- Modifying mutable fields of an `AgentEvent` can affect subsequent execution. +- A `StreamEvent` is only an output payload. Iterating drives the model loop; closing the stream ends a run that has not finished. +- A `TapeEntry` is for later queries and does not change the Step that is executing; only entries selected by `TapeContext` become input for later model calls. -The examples below use the `agent` from the [SDK guide](/docs/sdk/): +## Registering handlers {#handlers} ```python from bub.core.events import StepFinished, ToolCallFinished @@ -56,101 +35,58 @@ def report_step(event: StepFinished) -> None: agent.events.on(StepFinished, report_step) +agent.events.off(StepFinished, report_step) # remove it when no longer needed ``` -The two registration forms are equivalent: the decorator suits a new function, and passing it directly suits an existing one. To remove it, call `agent.events.off(StepFinished, report_step)`. - -A handler registered on a base class also receives subclass events. Every agent event derives from `AgentEvent`, so a handler registered for `AgentEvent` receives all of them — useful for uniform logging or timing, but watch the volume. +The decorator and passing the function directly are equivalent. A handler registered on a base class also receives subclass events; listening to `AgentEvent` handles all Agent events uniformly. ### Delivery rules {#delivery} -Four rules decide what a handler may assume about ordering: - -1. **Registration order.** Within one dispatch, matching handlers run in the order they were registered. -2. **Async handlers finish first.** The bus awaits one async handler before calling the next. -3. **Return values are ignored.** Whatever a handler returns, the bus discards. To influence the run, mutate fields on the event. -4. **Exceptions propagate.** A handler exception is raised out of `emit()`, and can fail the whole run. - -Rule 4 catches people. A handler that only observes should catch its own errors: - -```python -@agent.events.on(ToolCallFinished) -async def audit_tool(event: ToolCallFinished) -> None: - try: - await send_to_audit_log(event.call.tool, event.result) - except Exception: - logger.warning("audit failed tool={}", event.call.tool) -``` +1. Each `emit()` calls the matching handlers in registration order. +2. When a handler returns an awaitable, the bus waits for it to finish before calling the next one. +3. Return values are ignored; to control the run, modify the event's fields. +4. Exceptions propagate to the caller of `emit()` and may fail the run. +5. Before each handler runs, `event.stopped` is checked; when it is `True`, this dispatch stops. -Another edge: **ordering applies to one dispatch only.** Concurrent tool calls or two runs may emit events at the same time. State shared between handlers is still yours to coordinate. +Handlers used only for logging or metrics should handle their own exceptions so they do not affect the main flow. The ordering guarantee applies only within a single `emit()`; when concurrent tools or multiple Runs share state, the caller still has to coordinate. -## Call control {#call-control} +## Controlling model and tool calls {#call-control} -To stop a call, assign the event's `decision` field. This is the only place in the event system where you can rewrite the loop's behavior. +`decision` is used to skip the original call; other mutable fields are used to replace the request, arguments, result, or subsequent messages. For example, forbid an order modification that lacks permission before it runs: ```python from bub.core.events import ToolCallDecision, ToolCallStarted @agent.events.on(ToolCallStarted) -def require_refund_permission(event: ToolCallStarted) -> None: - if event.call.tool == "refund_order" and not event.state.get("refunds_allowed"): - event.decision = ToolCallDecision.deny("Refunds require approval.") +def check_order_update(event: ToolCallStarted) -> None: + if event.call.tool != "update_order": + return + if not event.state.get("orders_writable"): + event.decision = ToolCallDecision.deny("This session does not allow order changes.") ``` -Three verdicts, three different effects: +`ToolCallStarted` supports three ways to take control: -| Verdict | Did the tool run? | What the model sees | Afterwards | +| Action | Is the tool executed? | Result | Subsequent handlers | | --- | --- | --- | --- | -| `deny(message)` | no | a tool error | this dispatch stops | -| `replace(result)` | no | the result you supply | this dispatch stops | -| change `event.call` | yes, with new arguments | the real result | dispatch continues | - -`deny()` skips the tool and produces a tool error. `replace(result)` skips the tool and supplies the result directly. Both stop this `ToolCallStarted` from propagating. The executor still emits `ToolCallFinished` for the outcome. In the model loop the error goes back to the model as a tool result; in a direct command call the error is raised to the caller. - -To change arguments and continue, replace `event.call` with `dataclasses.replace(event.call, arguments=...)`. Note that this is a replacement, not a mutation: the event objects are frozen. - -To end before the model call, set `event.decision = LLMCallDecision.finish(text)` on `LLMCallStarted`. That stops later handlers and skips the provider call entirely. A skipped provider call emits no `LLMCallFinished` — **your timing and billing code must not assume the two events come in pairs**. +| `deny(message)` | No | A tool error | Dispatch stops | +| `replace(result)` | No | The specified result | Dispatch stops | +| Replace `event.call` | Yes, with the new call | The actual execution result | Dispatch continues | -### Event reference {#event-reference} - -These are all the agent events. Signatures and full fields are in the [Python API reference](/docs/reference/python-api/). - -| Event | When | What you can do | -| --- | --- | --- | -| `RunStarted` | A model run starts. | Read the prompt, model, and metadata. | -| `RunFinished` | The run's stream closes. | Read status, step count, elapsed time, and the error. | -| `StepStarted` | A model step starts. | Read progress. | -| `StepFinished` | A model step ends. | Read progress. With `status == "continue"`, set `next_prompt` to add a user message for the next step. | -| `MessagesPending` | A step builds its model request, or a step ends without tool calls. | Append user messages to `messages`, ordered before the next prompt. Messages added at the end of a step make the run continue. | -| `LLMCallStarted` | Before the provider call. | Replace `request`, or set `LLMCallDecision.finish(text)`. | -| `LLMCallFinished` | The provider call completes or raises. | Read the accumulated result. Cancellations and consumer closes are not reported. | -| `ToolCallStarted` | Before a tool runs. | Replace `call`, deny the call, or replace the result. | -| `ToolCallFinished` | A tool succeeded, failed, was denied, or was replaced. | Assign to `event.result.result` to change the result. | -| `ChatFinished` | This step's round-trip can be recorded. | Read the round-trip. | -| `CommandFinished` | A direct command completed or failed. | Read the command, status, and output. | +A denied or replaced tool still emits `ToolCallFinished`. The model loop returns the error to the model as a tool result; a direct command raises the error to the caller. -`MessagesPending` deserves its own note: it is the entry point for **interjecting during a run**. `StepFinished.next_prompt` sets the next step's user message, and `MessagesPending` appends messages at the last moment before the request is built. Steering is implemented on exactly these two points — something the user adds while the model is thinking does not have to wait for the next run. +The event itself is a mutable dataclass, but payloads such as `event.call` and `event.request` are frozen objects. When replacing arguments, create a new payload with `dataclasses.replace()` instead of modifying fields in place. -A command prompt runs tools directly, emitting tool events and `CommandFinished`. See [calling tools without the model](/docs/sdk/tools/#call-a-tool-without-a-model). +Setting `event.decision = LLMCallDecision.finish(text)` on `LLMCallStarted` skips the model provider and produces text directly. In that case no corresponding `LLMCallFinished` is emitted. -Every agent event carries the run's `state`. Events derived from `TapeEvent` also carry the current tape. Tool events expose `event.call.context` when the call has one. +## Recording conversations and custom events {#recording} -## Recording {#recording} +The Agent registers `TapeRecorder` before your handlers. The recorder subscribes to `RunStarted`, `RunFinished`, `StepStarted`, `StepFinished`, `ChatFinished`, and `CommandFinished`. -The agent registers TapeRecorder before your handlers. It records `RunStarted`, `RunFinished`, `StepStarted`, `StepFinished`, `ChatFinished`, and `CommandFinished`. +Among them, `ChatFinished` saves the conversation and tool round-trips, and later model calls build their context from these records. Tracing and Tape recording are independent of each other; turning tracing off does not stop recording. -`ChatFinished` is the important one: that handler writes the messages and tool round-trips, and later model calls get their context from what it wrote. In other words, **TapeRecorder is not optional logging — it is the producer of the context**. Delete the round-trips from the tape and the next run reads nothing of what happened. - -Recording is part of the normal flow. It writes to the store you configured, and to memory when you configure none. Recording is independent of tracing settings: you can turn tracing off and recording continues. - - - -## Custom events {#custom-events} - -Define a dataclass that inherits `AgentEvent`. Inherit `TapeEvent` instead when handlers need the tape. +Custom events inherit from `AgentEvent`, or from `TapeEvent` when they need the current Tape: ```python from dataclasses import dataclass @@ -175,24 +111,45 @@ async def mark_reviewed() -> None: ) ``` -A custom event inherits three capabilities: the same delivery rules, access to the run's state and tape, and the ability to be intercepted. - -`emit()` returns the event once the handlers have run. A custom mutable event can carry a result back to the emitter. To stop delivery as soon as the outcome is settled, override its `stopped` property — that is how the builtin `ToolCallStarted` lets `deny` and `replace` terminate early. +`TapeRecorder` does not record custom events automatically; the example above writes one explicitly in a handler. Custom events also have no built-in `decision` protocol; when you need to return a result, define mutable fields yourself, and when you need to stop dispatch early, override the `stopped` property. Tools can emit events through `context.events`. -Tools can emit events through `context.events`, using that same run state. +## Using hooks in the app {#app-plugins} -## App plugins {#app-plugins} +`AgentPlugins` connects application hooks to Agent events: -The kernel uses events; the app uses hooks. `AgentPlugins` is the bridge. +| Application hook | Agent event | +| --- | --- | +| `before_llm_call` / `after_llm_call` | `LLMCallStarted` / `LLMCallFinished` | +| `before_tool_call` / `after_tool_call` | `ToolCallStarted` / `ToolCallFinished` | +| `continue_prompt` | `StepFinished` | +| `provide_steering_inbox` | `MessagesPending` | -It maps four hooks onto call events: `before_llm_call`, `after_llm_call`, `before_tool_call`, and `after_tool_call`. It maps `continue_prompt` onto `StepFinished` and the steering inbox onto `MessagesPending`. Plugins return values as each hook requires, and the adapter changes the corresponding event. +Steering injects supplementary messages into later model requests of the current Run through `MessagesPending`. It differs from queueing: queued messages wait until the current Turn ends. The event layer provides the injection point; admission and queueing policies are decided by the channel and the application. -`BubAgent` uses this adapter, and a core Agent [can use it the same way](/docs/sdk/reuse-plugins/#other-hooks). +`BubAgent` uses this adapter by default, and a core Agent can also [connect it explicitly](/docs/sdk/reuse-plugins/#other-hooks). -The adapter follows plugin hook ordering. When a single interception hook raises, it catches, logs, and skips — **the opposite of a handler registered directly on the EventBus**, where exceptions propagate. The difference is deliberate: plugins come from third parties, and one broken plugin should not abort a whole run. +The two ways of connecting have different exception policies: exceptions from direct EventBus handlers propagate to the publisher, while exceptions from interception hooks are logged and skipped. To reject a call, return the corresponding decision. For full signatures, see the [Hooks reference](/docs/reference/hooks/#interception-through-pluginagenthooks); for examples, see [Application hooks](/docs/app/hooks/). -To deny a call, return the corresponding decision. Other app hooks follow their own error rules. +`framework.events` is another, independent bus that publishes only the application-level `CommandChanged`, `WorkChanged`, and `PromptChanged`; it does not carry model-call or channel events. -For plugin examples, see [Hooks](/docs/app/hooks/). For ordering and signatures, see the [hooks reference](/docs/reference/hooks/#interception-through-pluginagenthooks). The EventBus is available in the base SDK install. +## Event reference {#event-reference} -`framework.events` is a separate bus that publishes app-level events: commands, channel work, and user input, through `CommandChanged`, `WorkChanged`, and `PromptChanged` from `bub.application`. Subscribing and unsubscribing are still `on()` and `off()`. The two buses are independent: `framework.events` knows nothing about model-call details, and `agent.events` knows nothing about channels. +| Event | When it fires | What can be read or modified | +| --- | --- | --- | +| `RunStarted` | A model Run starts | `model`, `prompt`, `metadata` | +| `RunFinished` | The Run's stream closes | `status`, `steps`, elapsed time, and error | +| `StepStarted` | A Step starts | `step`, optional `prompt` | +| `StepFinished` | A Step ends | Progress and usage; set `next_prompt` to continue | +| `MessagesPending` | While building a request, or when a particular Step ends | Append user content to `messages` | +| `LLMCallStarted` | Before calling the model provider | Replace `request`; set `LLMCallDecision.finish()` | +| `LLMCallFinished` | The provider call succeeds or fails with an ordinary exception | `request` and the aggregated `result` | +| `ToolCallStarted` | Before a tool runs | Replace `call`; set `deny` or `replace` | +| `ToolCallFinished` | A tool succeeds, fails ordinarily, is denied, or is replaced | Assign to `event.result.result` to replace the result | +| `ChatFinished` | When the current Step's conversation and tool round-trips can be recorded | Messages, tool round-trips, usage, and errors | +| `CommandFinished` | A direct command succeeds or fails | Command, status, and output | + +`RunFinished` is emitted when the stream closes; a Run closed before it is iterated ends as `cancelled` with zero Steps. The Finished events for models and tools are not guaranteed to cover cancellation paths, so billing and cleanup cannot rely on them alone. + +`StepFinished.next_prompt` sets the next Step's user message, and `MessagesPending.messages` receives messages that arrived during the run. When `BUB_MAX_STEPS` is reached, the run raises a `max_steps_reached` error. + +A direct command emits tool events and `CommandFinished`, but no `RunStarted` / `RunFinished`. Every Agent event has `state`; subclasses of `TapeEvent` also carry the current `tape`. diff --git a/website/src/content/docs/docs/concepts/extension-points.mdx b/website/src/content/docs/docs/concepts/extension-points.mdx index 8175faacd..7a64a2c32 100644 --- a/website/src/content/docs/docs/concepts/extension-points.mdx +++ b/website/src/content/docs/docs/concepts/extension-points.mdx @@ -1,94 +1,87 @@ --- title: Extension points -description: Decide whether your task needs a tool, an event handler, a skill, a plugin, or a channel. +description: Depending on the task, choose a tool, a skill, an event handler, a plugin, or a channel. --- -import { Aside, Card, CardGrid, Steps } from '@astrojs/starlight/components'; +Before choosing an extension point, first consider whether you are adding an action, operating instructions, or a change to how a run works. Different extension points can be combined, but the same rule should have only one implementation. -The previous three pages describe how Bub runs itself. This one describes where your addition belongs. +## Choose by goal {#choose} -First decide what behavior you are adding. A new action is a tool. A new place to receive messages is a channel. These capabilities stack, but do not let two of them do the same job. - -## Ask three questions {#three-questions} - -When you want Bub to do X, ask in order: +| Goal | Extension point | How to connect it | +| --- | --- | --- | +| Query or modify an external system | [Tool](/docs/sdk/tools/) | `Agent(tools=[...])`, or the app's `provide_tools()` | +| Give the Agent a set of reusable operating instructions | [Skill](/docs/app/skills/) | A workspace skill directory, or shipped with a plugin package | +| Hand an independent subtask to another Agent to run | [Subagent](/docs/app/subagents/) | The built-in `subagent` tool, which can restrict the session, model, tools, and skills | +| Observe, rewrite, or block model and tool calls | [Agent events](/docs/concepts/events/#call-control) | `agent.events.on()`; app plugins use interception hooks | +| Change how history is converted into model messages | [TapeContext](/docs/concepts/tape/#context) | `Agent(context=...)`, or `build_tape_context()` | +| Replace history storage | [Storage integration](/docs/integrations/storage/) | `Agent(store=...)`, or `provide_tape_store()` | +| Replace the model client or execution environment | Core components | Constructor arguments; the app environment uses `provide_environment()` | +| Change message preparation, state saving, or reply sending | [Application hooks](/docs/app/hooks/) | A plugin implements the corresponding Turn hook | +| Connect a new messaging platform or backend service | [Channel](/docs/app/gateway/) | Implement Channel and provide it through `provide_channels(message_handler)` | +| Make an extension take effect automatically once installed | [Plugin](/docs/app/plugins/) | Register it in the `bub` entry point group | - -1. **Does X run code or reach an external system?** Yes → tool. No → next question. -2. **Is X a procedure, convention, or checklist?** Yes → skill. No → next question. -3. **Does X change how Bub itself behaves — how messages arrive, how they are processed, where they go?** Yes → plugin or channel. - +For example, an order plugin can provide: -If none match, you are most likely changing the kernel's model loop, which means assembling an Agent with the SDK directly. +- tools to query and modify orders; +- a skill describing the after-sales process; +- an interception hook that checks permissions before an order is modified; +- a Tape handler that saves order events. -## Choose {#choose} +They handle execution, process, constraints, and recording respectively; there is no need to cram all four kinds of logic into one function. -| You want to | Use | Which layer | Start at | -| --- | --- | --- | --- | -| Let the agent read or change something | [Tool](/docs/sdk/tools/) | A general kernel capability. Pass it to the Agent, or register it in the app. | [Registering tools](/docs/app/tools/) | -| Watch or stop a model or tool call | [Event handler](/docs/concepts/events/#call-control) | The kernel's EventBus. App plugins use the matching interception hooks. | [Hooks](/docs/app/hooks/) | -| Change which history becomes model messages | [TapeContext](/docs/concepts/tape/#context) | Pass it to the Agent, or provide it through an app hook. | [Replacing storage and context](/docs/integrations/storage/) | -| Teach the agent a reusable procedure | [Skill](/docs/app/skills/) | The Bub app discovers and loads it. | [Skills](/docs/app/skills/) | -| Change how the app initializes or how messages are handled | [Plugin](/docs/app/plugins/) | `BubFramework` and its hooks. | [Plugins](/docs/app/plugins/) | -| Connect a messaging service | [Channel](/docs/app/channels/) | The app's inbound and outbound routing. | [Channels](/docs/app/channels/) | +## Kernel assembly and app discovery {#kernel-and-app} -## Kernel and app {#kernel-and-app} +The core layer is assembled explicitly by the caller: -The same capability usually has one entry in each layer. That is not duplication; it is two ways of assembling the same thing: +```python +agent = Agent( + model=model, + tools=[order_tool], + store=store, + context=context, +) +``` - - - `Agent(tools=[...], context=..., store=...)`. +The application layer assembles the same kinds of components through hooks such as `provide_tools`, `build_tape_context`, and `provide_tape_store`, and then `BubFramework.load_hooks()` loads the built-in implementations and installed plugins. - For services that manage their own lifecycle and embed Bub. No settings file, no plugin discovery. - - - `provide_tools`, `build_tape_context`, `provide_tape_store`. +The choice rests on **who is responsible for assembly and lifecycle**: - For distributing to others, or when it should work as soon as it is installed. Plugins can be discovered, configured, and reused. - - +- Embedding into an existing service, where the host manages dependencies and resources: use the core-layer constructor arguments. +- Wanting an extension to take effect automatically after installation, or distributing it to others: use an application plugin. -The deciding question is **who owns the lifecycle**. If the code lives in your service and you decide everything at startup, use the kernel constructor arguments. If it should take effect after someone runs `pip install`, write a plugin. +When using core components directly, you can also integrate [application plugins](/docs/sdk/reuse-plugins/) as needed. ## Tools and skills {#tools-and-skills} -A tool runs Python and returns a result. A skill is a directory with a `SKILL.md` and optionally other files. Its instructions can tell the agent how to use a tool. - -The boundary between them gets confused often, so it is worth stating plainly: **a skill runs no code**. It is procedure written for the model — "read these files before changing code", "write the PR description with this template". Python functions mentioned in the instructions are yours to write and attach separately. +A tool is a type-annotated Python function that performs an action. A skill is a directory containing `SKILL.md` and supporting files that tells the model how to complete a class of tasks. | Dimension | Tool | Skill | | --- | --- | --- | -| Content | A Python function with type hints | Markdown instructions plus supporting files | -| Who reads it | the model, calling it through a schema | the model, once in context, then following it | -| Effect | Performs an action, returns data | Changes how the model works | -| When loaded | The available toolset for each run | Discovered by the app and added to the prompt as needed | -| Distribution | Registered through a plugin | Lives in the workspace or ships with a package | - -A plain Agent uses only the tools and prompt sections you give it. The Bub app discovers skills and also reads `AGENTS.md`. See [Skills](/docs/app/skills/) and [Tools](/docs/app/tools/). +| Content | Python functions and a parameter schema | Markdown instructions and supporting files | +| Purpose | Execute code and access external systems | Define processes, constraints, and operating methods | +| Loading | The available tools are provided on every model call | Each step lists names and descriptions; the body is expanded when the skill is referenced | +| Distribution | Passed directly to the Agent, or registered by a plugin | Placed in the workspace, or distributed with a package | -The agent can also call a tool through a [text command](/docs/sdk/tools/#call-a-tool-without-a-model), or have CodeMode write Python that calls it. Every path runs the same tool implementation — **one function, three triggers** — so a tool behaves the same whichever path invokes it. +Scripts in a skill do not run automatically just because the files exist. When code needs to run, it still happens through a tool call; the skill can guide the model on when to call it. -## Messages and state {#messages-and-state} +The same tool can be called directly by the model, called by Python generated in code mode, or called through a [text command](/docs/sdk/tools/#call-a-tool-without-a-model). All three paths share the handler and parameter validation, but their output and error behavior can differ. -A channel receives an inbound envelope and sends outbound ones. The app uses envelopes to carry content, routing, and session identity. All the hooks share the current turn's `TurnState` dictionary. +## Messages, state, and records {#messages-and-state} -The three have clearly separate jobs: - -- An **envelope** carries "where from, where to", across a process boundary. -- **`TurnState`** carries "what this turn remembers", living for a single turn. -- A **tape** carries "what already happened", across turns. - -To add a new message source, implement a channel that produces envelopes. To add behavior at a specific stage, implement a plugin hook that reads `TurnState`. To leave a long-lived record, write to the tape. - -[Turn](/docs/concepts/turn/) prepares the state and prompt before calling the agent. If you use the SDK directly, you hand the prompt and state to the Agent yourself. +| Object | Responsibility | Lifetime | +| --- | --- | --- | +| `Envelope` | Carries message content, routing, and session information | One inbound or outbound message | +| `TurnState` | Shares this round's data between hooks, tools, and event handlers | One Turn or Run | +| Tape | Saves the conversation, tool round-trips, and explicitly written records | Determined by storage; can span Turns and processes | -## Keep one owner per job {#one-owner} +New message sources produce an `Envelope` through a Channel. Data used only in the current Turn goes into `TurnState`; long-term facts are written to the Tape or to application storage. Not every hook receives `TurnState`; when you need state, declare the parameter according to the corresponding signature. -This is the most common extension-design mistake, and the cost is that nobody can later say which place is authoritative. +## Reviewing an extension design {#one-owner} -- If you already block refunds in `before_tool_call`, do not repeat the check inside the tool function. -- If a skill already documents the commit convention, do not duplicate it in `AGENTS.md`. -- If `provide_tape_context` already selects the context, do not rebuild history again in `build_prompt`. +Before implementing, make these boundaries clear: -A simple test: **if you deleted this logic, would another path fail to cover it?** If two paths both cover it, delete one. +- **A rule has only one authoritative source**: domain constraints are guaranteed by the tool or the domain layer; a unified policy can live in a hook, but do not maintain two judgments that may diverge. +- **Each kind of instruction has its own purpose**: `AGENTS.md` provides workspace rules, while skills provide task instructions that are expanded on demand. +- **Context is not concatenated twice**: `TapeContext` selects history, and `build_prompt` prepares the current input. +- **Errors have a definite destination**: decide whether an exception should propagate, be logged and skipped, or be converted into a tool result. +- **Resources have an owner that closes them**: make the lifecycle of connections and streams explicit, and decide how concurrent runs in the same session coordinate. diff --git a/website/src/content/docs/docs/concepts/tape.mdx b/website/src/content/docs/docs/concepts/tape.mdx index 332cf354d..c2d270780 100644 --- a/website/src/content/docs/docs/concepts/tape.mdx +++ b/website/src/content/docs/docs/concepts/tape.mdx @@ -1,188 +1,172 @@ --- -title: Tape -description: Where the conversation is stored, what the next model call reads, and what survives a run. +title: Tape (conversation storage and model context) +description: Tell persistent history apart from model context, and manage storage, anchors, run forks, and sidecars. +sidebar: + label: Tape --- import { Aside } from '@astrojs/starlight/components'; -An agent needs two things. It needs a record of its work. It also needs a useful set of messages for the next model call. Bub puts the record in a tape and builds the model context from the entries you select. +A Tape stores the Agent's run records, and `TapeContext` selects from those records the input for the next model call. **The stored history is not the context the model sees**: history can keep growing, while each request reads only a slice of it. -Keeping the two apart is the most useful decision in Bub: you can keep a complete history and still send a short request. This page covers how each side works and where the edges are. +`core.Agent` and `BubAgent` share this mechanism; their default stores and Tape naming schemes differ. -`core.Agent` and `BubAgent` share this model. Their default stores and session names differ. - -## One conversation, two readings {#history-and-context} - -Start with this distinction; everything below hangs off it. +## History and context {#history-and-context} ```mermaid flowchart LR - subgraph store["store"] - E1["anchor
session/start"] - E2["message
user: where is order A123"] - E3["tool_call
lookup_order"] - E4["tool_result"] - E5["message
assistant: shipped"] - E6["event
loop.step"] - E7["anchor
order-reviewed"] - E8["message
user: and the shipping cost"] - E1 --- E2 --- E3 --- E4 --- E5 --- E6 --- E7 --- E8 - end - - E1 -.->|"kept"| A["full history
every entry readable"] - E7 ==>|"enters the context"| B["model reads
only after the anchor"] + E1["anchor
session/start"] --- E2["message"] --- E3["tool_call"] --- E4["tool_result"] --- E5["anchor
order-reviewed"] --- E6["message"] + E1 -.-> A["history
query raw entries in order"] + E5 ==>|"default context start"| B["context
conversation and tool round-trips after the anchor"] ``` -- **History** is every entry in write order, append-only. Incident review, audit, and debugging read it. -- **Context** is a filtered slice, converted to Republic messages and handed to the model. The model never sees what was filtered out. +- **History** consists of Tape entries, queried in write order through `query()`. +- **Context** is built by `TapeContext` filtering entries and converting them to Republic messages, through `read_messages()`. -The filter is `TapeContext`, and its default rule is "conversation entries after the last anchor". +Normal recording only appends and does not modify old entries; explicit operations such as `reset()` clear the records, so a Tape is not an immutable event log. -## Storage {#storage} +## Default context rules {#context} -A plain `Agent` creates an `InMemoryTapeStore` by default. That history lives as long as the store. To keep it between processes, pass a file store: +The Agent uses `default_tape_context()` by default, selecting conversation and tool round-trips after the most recent anchor: -```python -from pathlib import Path +| Entry type | Content | Handling in the default context | +| --- | --- | --- | +| `message` | Conversation messages from the user, assistant, system, and so on | Converted to the corresponding message | +| `tool_call` | A tool call requested by the model | Converted to an assistant message carrying the tool calls | +| `tool_result` | A tool execution result | Paired with the earlier call and converted to a tool message | +| `anchor` | The context starting point and optional state | Used as a query boundary; not included in the result | +| `event`, `system`, `error` | Business events, system prompt changes, or record errors | Skipped | -from bub.core import Agent -from bub.core.store import FileTapeStore +Entries with `context=False` are filtered out before selection. Ordinary business `event` entries also do not enter model requests by default; to let the model read them, you must customize the selection logic. -agent = Agent(store=FileTapeStore(Path("sessions"))) -``` +`default_tape_context()` rebuilds tool round-trips. A directly constructed `TapeContext()` with no selector only converts `message` entries; do not mix the two. -Each `agent.run(..., tape="customer-42")` uses the tape named `customer-42` in that store. The same name continues the earlier conversation; a different name starts a separate one. `Agent` uses the name you give it as is. The app has its own workspace and session naming rules; see [app storage](#app-storage). +## Dividing phases with anchors {#anchors} -`agent.tape(name)` gives you the tape outside a run. Writes through it go straight to the store: +`handoff()` writes an anchor and moves the default context's starting point there; the earlier records remain: ```python -async def inspect_history() -> None: - tape = agent.tape("customer-42") - messages = await tape.read_messages() - entries = list(await tape.query().all()) - print(len(messages), "model messages;", len(entries), "stored entries") +async def start_next_phase() -> None: + await agent.tape("customer-42").handoff( + name="order-reviewed", + state={"summary": "Order A123 has shipped; the customer needs tracking details next."}, + ) ``` -`read_messages()` applies the context rules; `query()` reads raw entries. One tape, two readings — the diagram above, in code. +`handoff()` does not generate a summary automatically, nor does it inject `state` into the next request automatically. When you need to keep a summary, generate it before the handoff and then provide it explicitly through the next prompt or a custom context selector. -To write your own store, implement `TapeStore` or `AsyncTapeStore` from `bub.core.store`. The store interface and `TapeQuery` filters are in the [Python API reference](/docs/reference/python-api/). Your store and your app handle persistence and coordination between processes; the [SQLAlchemy guide](/docs/integrations/storage/) shows a complete replacement. +`Agent.session()` and `Agent.start()` create a `session/start` anchor for a new session; calling `agent.tape(name)` alone does not create one. -## Context {#context} +### Choosing another starting point {#custom-selectors} -Tape entries are messages, tool calls, tool results, events, and anchors. Normal recording only appends. To clear history, call an explicit operation such as `Tape.reset()`. +`TapeContext.anchor` determines the query range: -The default `TapeContext` selects conversation entries after the last anchor, then converts them to Republic messages: - -| Entry | Model context | +| Value | Query range | | --- | --- | -| `anchor` | The default query excludes it. If a custom query selects an anchor, the default selector turns it into an assistant message. | -| `message` | The stored message. | -| `tool_call` | One assistant message carrying the calls. | -| `tool_result` | A tool message, paired with the earlier call. | -| Other events | The default selector skips them. | +| `LAST_ANCHOR` (default) | After the most recent anchor | +| `"order-reviewed"` | After the anchor with the specified name | +| `None` | No anchor filtering | -Note the last row. **Events do not enter the context by default**, so what you record on the tape and what the model sees are two different things. That is both a freedom and a trap: you can record plenty of useful information that the model never reads. +When you want the default message conversion but a different starting point, use `dataclasses.replace()`: -### Cutting context with an anchor {#anchors} +```python +from dataclasses import replace -An anchor marks a new starting point for the context. `handoff()` writes one, and earlier entries stay: +from bub.core import Agent, default_tape_context -```python -async def start_next_phase() -> None: - await agent.tape("customer-42").handoff( - name="order-reviewed", - state={"summary": "Order A123 has shipped; the customer needs tracking details."}, - ) +agent = Agent( + context=replace(default_tape_context(), anchor="order-reviewed"), +) ``` -The `state` here is stored on the anchor, before the default query's start. **To bring the summary into the next phase you must put it into the next prompt, or select it explicitly in your context strategy** — a handoff does not hand anything to the model by itself. - -This is the most common misunderstanding about handoff: it is a mechanism for **forgetting**, not for remembering. It decides what old entries become invisible; it does not compress them into something new. +When you need a completely different projection, pass a selection function to `TapeContext(select=...)`. It receives the entries and context, and returns a list of Republic messages or an awaitable of one. A custom function replaces the default selector, so ordinary conversation, tool round-trips, and business events are yours to compose as needed. -A new session gets a `session/start` anchor automatically, so the first run has a starting point too. +### Context too long {#context-overflow} -### Choosing another rule {#custom-selectors} +When the Agent recognizes a context-length error, it performs one automatic handoff and retries the original prompt. This only moves the context's starting point; it does not generate a summary, and if the remaining request is still too large, it fails again. -To choose another anchor, pass `context=TapeContext(...)` to `Agent`. You can also supply your own `select(entries, context)` function: +To manage context against a budget on purpose, use the [`bub-context-budget` plugin](/docs/sdk/reuse-plugins/), which lets the model call `tape.handoff` as it approaches the limit. -```python -from bub.core import Agent -from bub.core.context import default_tape_context -from bub.core.tape import TapeContext +## Run state versus persistent records {#state} +The `state` passed to `agent.run()` is a mutable dictionary shared for that run. It is not persisted automatically and is not restored on the next run. -def select_recent_business_events(entries, context): - """Hand business events to the model, not just messages.""" - return default_tape_context().select(entries, context) +| What you need to keep | Where it goes | +| --- | --- | +| A temporary value used only during this run | `state` | +| A business fact you need to query later | Write it to the Tape with `append_event()` | +| Structured state to restore on the next run | Application storage, or an anchor's `state` plus explicit loading | +| The conversation and tool round-trips | Written to the Tape by `TapeRecorder` | +`TapeRecorder` records only the conversation, tool round-trips, and selected lifecycle events; it is not a complete auditor. Custom business events need to be written explicitly, and whether they enter the model context is still decided by `TapeContext`. -agent = Agent(context=TapeContext(anchor=None, select=select_recent_business_events)) -``` +## Run forks {#forks} -A selector returns Republic messages, or an awaitable of them. `default_tape_context()` includes tool round-trips; a `TapeContext` without `select` uses a simpler message conversion. Entries with `context=False` are dropped before either the default or a custom selector runs. +Each `agent.run()` records new entries on a fork of the Tape. When the stream closes, the fork merges back into the parent Tape by default; the merge runs on normal completion, exceptions, cancellation, and stopping consumption early. -### When the context overflows {#context-overflow} +```python +async with agent.run("Continue processing the order", tape="customer-42") as stream: + async for event in stream: + ... +``` -When the agent recognizes a context-length error, it makes one automatic handoff and retries with the shorter context. +Pass `merge_back=False` to discard this fork's records on exit. You can also manage the scope yourself with `agent.session(name, merge_back=...)` or `tape.fork_tape(merge_back=...)`. -To manage context against a budget on purpose, the [`bub-context-budget` plugin](/docs/sdk/reuse-plugins/) is a working example: it asks the model to call `tape.handoff` as the limit approaches. +When multiple runs need to read each other's completed results, the application coordinates the execution order. `FileTapeStore` does not provide a session lock across workers; the [FastAPI example](/docs/sdk/web-service/) demonstrates locking within a single process. -## State {#state} +## Sidecars {#sidecars} -The `state` that you pass to `agent.run()` is a mutable dictionary. Tools, prompt sections, and event handlers share it during the run. +A `TapeSidecar` stores associated data in another Tape that is paired with the main Tape. Once mounted through `Agent(sidecars=...)`, the sidecar forks, merges, and resets along with the main Tape, but its entries do not enter the main context. -It is **not** persisted. When the run ends, nothing owns the dictionary. Your app owns its persistence: keep durable data in your own store, or write selected facts to the tape and read them back when you need them. +The Bub app uses a spill sidecar to store large tool results. The main conversation keeps only a preview and a handle, and the Agent then reads the result in chunks through `spill.read`. The trigger threshold is configured by [`BUB_SPILL_THRESHOLD`](/docs/reference/settings/#spill-settings). -The default [TapeRecorder](/docs/concepts/events/#recording) saves conversation exchanges and selected lifecycle records. To keep other variables, custom events, or decisions, record them yourself — either in `state` for the duration of the run, or with `append_event` on the tape for the long term. +## Media recording boundaries {#media} -| What you need to keep | Where it goes | -| --- | --- | -| A value computed during this run | `state`, discarded when the run ends | -| A business fact you must query later | an `event` entry on the tape | -| Structured data to read back next run | your own store, or an anchor's `state` plus explicit loading | -| A fully auditable conversation | the tape itself (TapeRecorder already does this) | +Model requests can contain images, audio, and video, but a Tape is not media file storage. When a Run's fork writes its records, it strips non-text content blocks from the prompt and keeps only text, avoiding copying inline media into the JSONL history. -## Forks {#forks} +When media must be replayed or audited later, the host saves the files to object storage or a filesystem and records a resolvable reference in the text. When a tool needs to hand media back to the current model call, use [`content_result()`](/docs/sdk/tools/#media-results). -Each `agent.run()` opens a fork of its tape. When the stream closes, the fork appends its new entries to the parent tape. +## Choosing storage {#storage} -That holds on the error path too: an exception or an early cancellation still merges. A failed run can therefore leave useful partial history, which is valuable when debugging. +A plain `Agent` uses `InMemoryTapeStore` by default, and records live only in the current process. To keep them across processes, pass a file store: -```mermaid -flowchart LR - P[("parent tape
existing entries")] - P --> F["fork
the run writes here"] - F -->|"stream closes / raises / cancelled"| M["merged into the parent"] - F -.->|"merge_back=False"| D["this run's writes dropped"] - M --> P2[("parent tape
has the new entries")] -``` - -Pass `merge_back=False` and the fork's records are dropped on exit. This affects tape writes only: side effects a tool already produced, such as a sent request or a changed file, remain. **A fork isolates records, not side effects.** - -To manage a piece of work yourself, use `agent.session(name, merge_back=...)` or `tape.fork_tape(merge_back=...)`. Both merge on exit by default, including on an exception. +```python +from pathlib import Path -To let two runs share a tape and see each other's finished work, chain them. Your app provides the session lock. The [FastAPI example](/docs/sdk/web-service/) shows locking within one process; multiple workers need separate coordination. +from bub.core import Agent +from bub.core.store import FileTapeStore -## Sidecars {#sidecars} +agent = Agent(store=FileTapeStore(Path("sessions"))) +``` -A `TapeSidecar` stores related data in a separate tape that still belongs to the same tape conceptually. +`agent.run(..., tape="customer-42")` uses the Tape of that name. The same name continues the existing records; different names are independent of each other. -A mounted sidecar forks, merges, and resets with the main tape. Its entries do not enter the main context, so they cannot crowd the model request. Pass one with `Agent(sidecars=...)` when your app needs it. +Outside a Run, you can read history through `agent.tape(name)`: -The Bub app mounts a spill sidecar for large tool results. It keeps a short preview and a handle in the conversation, and the agent can then read parts of the result with `spill.read`. This is another way to apply "history can be large, context must be small": move the bulk aside instead of deleting it. +```python +async def inspect_history() -> None: + tape = agent.tape("customer-42") + messages = await tape.read_messages() + entries = list(await tape.query().all()) + print(f"model messages: {len(messages)}, stored entries: {len(entries)}") +``` -If a sidecar fails during a fork merge or a tape reset, the failure is recorded separately and the main tape operation still completes — a sidecar is an add-on, and a broken one should not take the main flow down. The spill limits are in the [app configuration](/docs/app/configure/). +`read_messages()` applies the context rules, while `query()` returns raw entries. A custom backend must implement `bub.core.store.TapeStore` or `AsyncTapeStore`; for a complete replacement example, see [Storage integration](/docs/integrations/storage/). ## App storage {#app-storage} -`BubAgent` uses the store that `BubFramework` provides. The built-in app uses `FuzzyFileTapeStore` under the Bub home directory, normally `~/.bub/tapes/`. +The built-in app provides `FuzzyFileTapeStore` through `provide_tape_store`, with the default directory `~/.bub/tapes/`. The Tape name is generated from the workspace path and the session ID together, so sessions with the same name in different workspaces do not share history. + +`BubAgent` chooses its store in the following order: -The tape name combines hashes of the workspace path and the session ID. The same session ID in two workspaces therefore has two separate histories: using one session name in `~/work` and `~/scratch` will not show you what the other said. +1. A `tape_store` passed explicitly; +2. The store returned by `framework.get_tape_store()` when the Agent was created; +3. The in-memory store when neither is present. -To change the store, the app uses `provide_tape_store`. To choose context, it uses `build_tape_context`. The [SQLAlchemy guide](/docs/integrations/storage/) shows that plugin route. For a service that uses the SDK directly, pass the implementation to `Agent(store=...)` as shown in [Storage](#storage). +To use the store provided by `framework.running()`, create the `BubAgent` inside that scope. Replace the store with `provide_tape_store`, and replace the context strategy with `build_tape_context`. -The built-in app restores some session settings from tape records. Any other per-turn state is yours to persist. +The built-in app restores session settings such as the model, reasoning effort, and code mode from the Tape; any other application state must be persisted by you. diff --git a/website/src/content/docs/docs/concepts/turn.mdx b/website/src/content/docs/docs/concepts/turn.mdx index 243bca65b..6e987f101 100644 --- a/website/src/content/docs/docs/concepts/turn.mdx +++ b/website/src/content/docs/docs/concepts/turn.mdx @@ -1,92 +1,99 @@ --- -title: Turn -description: One inbound message, from the session lookup to the reply that leaves the app, and where each stage leaves room for a plugin. +title: Turn (message processing flow) +description: Understand Turn, Run, and Step, and how a message travels through the Bub app to get a reply. +sidebar: + label: Turn + badge: { text: 'App', variant: 'note' } --- -import { Aside, Steps } from '@astrojs/starlight/components'; +A **Turn** is the complete process of handling one inbound message in the app: find the session, prepare the input, run the model, save state, and send the reply. It keeps that common work outside the Agent loop, so tool and model code only have to focus on the current task. -A turn is one inbound message, and all the work that it causes. `BubFramework` handles a turn: it finds the session, loads the state, builds the prompt, runs the model, and delivers the reply. Each stage has a hook, so a plugin can add to one stage or replace it. +## Turn, Run, and Step {#definition} -A turn belongs to `bub[app]`. A direct `core.Agent.run()` call has no turn — the loop starts with the prompt, state, and tape that you give it. This page covers the layer between a channel and that loop. - -## Turn, run, and step {#turn-run-step} - -There are three granularities of "one" here. Separate them first: +A Turn usually starts one **Run**. A Run is the loop between model and tools, and each loop iteration is a **Step**: ```mermaid flowchart LR - T["Turn
all the work one message causes"] --> R["Run
one agent.run() call"] --> S["Step
one model call + the tools it requires"] + T["Turn
handle one message"] --> R["Run
model and tool loop"] + R --> S1["Step 1
model call + tool calls"] + R --> S2["Step N
model answers directly"] ``` -turn ⊃ run ⊃ step: a turn usually contains one run, and a run contains multiple steps. The reverse does not hold — when using the SDK directly, an `agent.run()` call can exist independently, with no turn; a step always belongs to a run. +A Step contains one model call plus every tool call that response requests. The Run ends when the model stops requesting tools and no messages are pending. `BUB_MAX_STEPS` limits the number of Steps, not the number of tool calls. -## Why a turn exists {#why-a-turn} +Not every run has all three layers: a direct command does not start a Run, `Agent.run_once()` executes a single Step, and a plugin can replace the whole model stage. When you use `core.Agent` directly, the caller is responsible for sessions, state, and reply routing. -With just a model loop, every call site has to answer the same questions again: which session is this message from? Should last round's state come along? Where does the reply go? +## The seven processing stages {#stages} -A turn lifts those out of tool and model code and fixes them into named stages. The payoff is concrete: +`BubFramework.process_inbound()` processes a message in the following order: -- **Every piece can be replaced**, but the replacement points have names, so you do not fork the whole flow. -- **The failing stage is explicit**: `on_error(stage=...)` receives its name. -- **One message is one transaction.** State loads at the start and saves at the end, and a failure in between has a defined cleanup. +| Stage | What it handles | Default behavior | +| --- | --- | --- | +| 1. `resolve_session` | Determine the session | Use the message's `session_id`; otherwise generate `channel:chat_id` | +| 2. `load_state` | Restore this round's state | Restore session settings and bind the Agent | +| 3. `build_prompt` | Build the model input | Recognize commands, and add context, time, and media for ordinary messages | +| 4. `run_model` / `run_model_stream` | Produce the model output | Call `BubAgent.run_stream()` | +| 5. `save_state` | Save this round's state | Exit the message lifecycle | +| 6. `render_outbound` | Generate the reply | Convert model text into outbound messages | +| 7. `dispatch_outbound` | Send the reply | Hand it to the corresponding channel | -## Stages {#stages} +Each stage has a corresponding hook. A plugin can add to the default behavior or replace the result according to the hook's contract; for parameters, return values, and priority, see the [Hooks reference](/docs/reference/hooks/#turn-hooks). -`framework.process_inbound(message)` runs these seven stages in order. +### Passing data between stages {#state} - +`TurnState` is an ordinary `dict[str, Any]`. Hooks, tools, and event handlers that receive `state` in their signature share the same dictionary. -1. **`resolve_session`**: select the session ID for the inbound message. The first non-empty result wins; when none is returned, a default is built from the channel and chat identifiers. +It exists only for the current Turn. Data that must survive across Turns should be written to storage in `save_state` and restored in `load_state`; the conversation and tool round-trips are stored by the Tape. -2. **`load_state`**: collect state fragments from the hook implementations and merge them into one `TurnState` dictionary. Lower priority merges first, and higher priority overrides. +When processing finishes, `process_inbound()` returns a `TurnResult`: -3. **`build_prompt`**: turn the inbound content into a prompt. The first result wins; when no hook provides one, the inbound content is used as is. +| Field | Content | +| --- | --- | +| `session_id` | The session ID for this turn | +| `prompt` | The input received by the model stage | +| `model_output` | The aggregated model text | +| `outbounds` | The replies that were generated and attempted | +| `state` | This turn's state dictionary | -4. **`run_model` / `run_model_stream`**: produce the model output, as a final result or as a stream. The built-in implementation calls `BubAgent.run_stream()`, which enters the loop described below. +Even when no sending channel is bound, the replies remain in `TurnResult.outbounds` so the host can handle them itself. -5. **`save_state`**: finish the work of the model stage. This stage runs in its own `finally` block, so an exception in the previous stage still reaches it. +## Choosing an entry point {#entry-points} -6. **`render_outbound`**: turn the result into outbound envelopes. Results from several hooks are concatenated; when all are empty, one default reply is used. +Choose the entry point according to how much of the process you need to handle: -7. **`dispatch_outbound`**: actually deliver each outbound envelope, through its channel. +| Entry point | Scope | The caller is responsible for | +| --- | --- | --- | +| `core.Agent.run()` | A model Run or a direct command | Session, state, and message routing | +| `BubAgent.run_stream()` | Optional initial state loading and a model Run | Saving state, generating and sending replies, and concurrency within the same session | +| `framework.process_inbound()` | The complete Turn | The application resource scope, concurrency within the same session, and channel dispatch | - +`process_inbound(stream_output=True)` delivers streaming output to the channel, and still returns a complete `TurnResult` when it finishes. -The first three stages prepare input, one stage produces output, and the last three send it out. Only stage 4 runs for long — the others wrap it on both sides. +## Run boundaries {#outside} -The built-in plugin supplies the default implementation of these seven stages. Other plugins can add behavior or replace it, as each hook's contract allows. The [hooks reference](/docs/reference/hooks/#turn-hooks) defines the first-result hooks, the combined results, and the priority rules. +A Turn does not own everything related to a message: -## What crosses the stages {#what-crosses-stages} +| Need | Owner | +| --- | --- | +| Decide whether to accept a new message, or hand it to the current Run | The `admit_message` hook and the channel admission policy | +| Merge consecutive messages that arrive close together | `BufferedMessageHandler` | +| Intercept a single model or tool call | Agent events or application interception hooks | +| Add a message for the next Step | `continue_prompt` or `MessagesPending` | +| Save the conversation and tool round-trips | `TapeRecorder` | -Exactly three things pass from stage to stage. Together they are the turn's complete internal state: +`process_inbound()` provides no session lock, and it is not a database transaction. Channels allow concurrency by default; side effects a tool has already produced, such as file changes or network requests, are not rolled back because a later step fails. -| Value | Type | Written by | Read by | -| --- | --- | --- | --- | -| `session_id` | `str` | `resolve_session` | every later stage, and `TurnResult` | -| `state` | `TurnState` | the `load_state` fragments; tools and handlers also mutate it during the run | prompt building, the model call, tools, `save_state` | -| `prompt` | `list[UserContent]` | `build_prompt` | the model stage | +## Errors and resources {#failures} -`state` is the one most easily overlooked. It is a plain dictionary that lives from loading to saving, and tools and event handlers receive that same object. To keep anything across turns you must write it out in `save_state` and read it back in `load_state` — the built-in app persists only selected settings. The [tape state section](/docs/concepts/tape/#state) covers where durable data belongs. +Once execution enters the model stage, `save_state` runs in a `finally` block, so state is still saved when the model stage fails. Session resolution, state loading, and input building happen before that `finally` is entered, so a failure in those stages does not call `save_state`. -## Agent call {#agent-call} +Unhandled exceptions go to `on_error` and are then re-raised. `on_error` is for observing and reporting errors; it does not restore the original Turn. -The built-in `run_model_stream` hook calls `BubAgent.run_stream()`. `BubAgent` uses the core model and tool loop, plus the app defaults: settings, skills, commands, steering, and the app tool policies. - -The framework converts between streaming and non-streaming hook results, so a plugin that implements only one of them still works. `process_inbound(..., stream_output=True)` lets the channel router read the output as it arrives, and still returns a complete `TurnResult` when the stream ends. +When hooks provide resources such as storage, process messages inside the `framework.running()` scope: ```python -result = await framework.process_inbound(message, stream_output=True) -print(result.session_id, len(result.outbounds)) +async with framework.running(): + result = await framework.process_inbound(message) ``` -Replacing `run_model` changes this step. An echo plugin, for example, can return text without creating an agent run — in which case no core events fire and no tape records are written. **Core events and tape records occur only when the chosen implementation actually uses the core agent**, which makes it the most direct way to tell how far a turn travelled. - -## Resources and failures {#resources-and-failures} - - - -If the model stage raises an unhandled exception, the framework runs `save_state`, then skips rendering and dispatch. The outer error path calls `on_error(stage="turn", ...)` and raises the exception again. The built-in error observer can send an error reply through outbound dispatch. A failure in one `on_error` observer does not stop the other observers. - -Use `async with framework.running():` when your hooks provide stores or other app 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`. +`bub run` opens the scope for one message; `bub chat` and `bub gateway` keep it open for as long as the app runs. When you need the storage that scope provides, create the `BubAgent` inside it as well. diff --git a/website/src/content/docs/docs/getting-started/index.mdx b/website/src/content/docs/docs/getting-started/index.mdx index d7b1f50e2..287a2e37e 100644 --- a/website/src/content/docs/docs/getting-started/index.mdx +++ b/website/src/content/docs/docs/getting-started/index.mdx @@ -14,10 +14,10 @@ So an agent rests on four things: a **model**, **tools**, a **context**, and the Composable agent capabilities: the loop, tools, context, the event bus, and the model client. - It calls models through [republic](https://getrepublic.org) and depends only on [pydantic](https://pydantic.dev) and [loguru](https://github.com/delgan/loguru). + It calls models through [republic](https://getrepublic.org); the remaining core dependencies are [pydantic](https://pydantic.dev), [loguru](https://github.com/delgan/loguru), and typing-extensions. - A ready-to-run harness: settings, plugins, hooks, skills, message channels, a gateway, and more. + A ready-to-run harness on top of the core: settings, plugins, hooks, skills, message channels, and a gateway. None of it needs to be built yourself; a little configuration is enough to start. @@ -37,17 +37,17 @@ Which one should you use? Bub is built on being simple, composable, and extensible. That means you can freely combine Bub's capabilities, or build your own agent on top of it: - - Each turn appends to the tape and never rewrites an entry. The model reads a slice of the tape, not a message array that grows without limit. + + Each turn appends to the tape and never rewrites an entry. The model reads a slice of the tape, not a message array that keeps growing. - - Model calls and tool calls each report a start, an end, and a result. A host can watch a run, change the next input, or stop it. + + Model calls and tool calls each emit a start, an end, and a result. A host can watch a run, rewrite the next input, or abort it outright. - - A Python function with type hints is enough. Code mode lets the model write a short program that calls several tools in one step. + + A type-annotated Python function is enough to become a tool. Code mode lets the model write a short program that calls several tools in one step. - - Every capability in Bub can come from a plugin. A plugin can be a tool, an event listener, a message channel, or a whole set of skills. + + Every capability in Bub can be provided by a plugin. A plugin can be a tool, an event listener, a message channel, or a whole set of skills. diff --git a/website/src/content/docs/docs/getting-started/install.mdx b/website/src/content/docs/docs/getting-started/install.mdx index fcd759bbf..7b5208574 100644 --- a/website/src/content/docs/docs/getting-started/install.mdx +++ b/website/src/content/docs/docs/getting-started/install.mdx @@ -32,9 +32,9 @@ uv tool update-shell
-The script installs uv and Python 3.12, and puts `bub[app]` into your current environment. +The install script installs uv and Python 3.12, and puts `bub[app]` into your current environment. `app` is an optional dependency group that bundles the application-layer capabilities such as the CLI, channels, and plugins; you do not need it if you only use the SDK. -When it finishes, run `bub --help`, then run `bub onboard` to start configuring, and confirm the command line works. +When it finishes, run `bub --help`, then run `bub onboard` to start configuring.