Provider plugins
A provider plugin is a bb plugin that registers one or more providers: the agent backends a thread can run on. Codex, Claude Code, Pi and every ACP agent bb ships are provider plugins, and they use exactly the API described here.
What changed recently #
- No
x-bb-timeline-grammarheader.GET /threads/:id/timelineand…/timeline/turn-summary-detailsserve grammar v3 rows to every reader, SDK or raw HTTP; there is no legacy projection behind a missing header. FAQ. - No
activityIntentson tool rows.TimelineToolWorkRowhas no such key and no reader of it; older mobile builds are not supported because the app ships with the server, so the timeline wire serves grammar v3 only. Compatibility. - No
experimental_provider*booleans onProviderInfo. The rowsexperimental_useProviders()andbb.sdk.providers.list()serve carrymaintenance { health, usage, installation }alone;experimental_providerHealth,experimental_providerUsageandexperimental_providerInstallationare removed outright, with no alias, so a typed read no longer compiles and the served row has no such key. The one-release window that served both was withdrawn before release because every client ships with the server. Compatibility.
Two review rounds over the pass below. Each line is something a plugin author, a bridge, or an SDK consumer can observe; the linked page carries the detail.
- The 0.4.15 provider-bridge exports whose definitions moved are back as aliases.
hostDaemonAcpLaunchSpecSchema,normalizeHostDaemonAcpLaunchSpec,claudeTaskToolNameSchema,claudeTaskToolOutputSchemaand the typesHostDaemonAcpLaunchSpec,ClaudeTaskToolOutputare exported again from@get-bb/plugin-sdk/provider-bridgeand listed under scheduled removals (next major), so a bridge built against 0.4.15 rebuilds. Reference. - Every 0.4.15 export the stabilization removed or renamed is named. Seven were removed outright (the four already listed plus the testing-kit internals
experimental_ConformanceClient,experimental_checkItemOpensBeforeDelta,experimental_diffCumulativeText); the rest follow a mechanical rename rule. Compatibility. - Extension kinds are accepted only from the owning plugin's threads. At ingest a
<pluginId>/<name>item or state must come from a thread whose provider that plugin registered; the check runs before the schema check, for both surfaces, and a foreign kind persists asprovider/unhandledwith a reason naming the kind, its owner and the plugin behind the thread's provider. Concepts. - The presentation glyph check runs on every item-bearing event: the thread-scoped delegation and background-task progress and completed snapshots as well as
item/startedanditem/completed, at ingest and in the conformance rule. Concepts. - A presentation
detailrenders images as alt text wherever it renders: the row body and the approval banner, on the web and on mobile. Concepts. - A blank
detailkeeps an extension row title-only.""or whitespace counts as absent for the expand rule and the CLI formatter, so no chevron opens onto an empty body. Concepts. - Which tints mobile paints. Hex,
rgb()/rgba()/hsl()/hsla()/hwb()with a numeric alpha, and named colours paint on every client;oklch(),oklab(),lab(),lch(),color()and a percentage alpha paint on the web only, and on mobile such a tint falls back to the neutral row colour, per theme side, never to black. Concepts. - A failed host build no longer hides a provider behind an AI service. A plugin that registers both from one
bb.hostentry keeps its provider listed as unavailable with the build error; the service is staged unbound and never goes live. Concepts. - The conformance kit pins
providerThreadIdonthread/resumeandthread/forkas well asthread/start:session/resume-identityalways,session/fork-identitywhen the handshake declares fork. The echo pins fifteen green rules now. Testing. - The fake plugin host refuses what the server refuses: a provider
iconor toolpresentation.icon.glyphnaming an undeclared or foreign icon (checked against the newexperimental_declaredIconNamesoption), and a provider from a plugin with nobb.hostentry. Testing. - The public-SDK scan reports relative escapes and dynamic specifiers: a relative import that resolves outside the package root is
outside-packageunless anallowpattern names it, and a non-literalimport()/require()argument isdynamic-specifier. Testing. - The presentation-less tool-row window, as pi uses it. The pi bridge's generic
toolrows carry no presentation yet, so its live read/grep/find/ls calls take the same read-time adapter as persisted pre-v3 rows and render as exploration rows; the window closes when pi stamps presentation and the backfill migration makes it required. Compatibility. - Host-daemon protocol 164. The server stops accepting any interaction lifecycle record from a daemon event batch; no daemon sends one, so nothing on the wire changed. Compatibility.
- Timeline grammar header, since withdrawn. This round added
x-bb-timeline-grammarand a grammar 2 projection for requests without it; both are gone again (see the entry above), and every reader gets v3 rows. FAQ. - A childless persisted delegation call stays a delegation row. A presentation-less
Agent,Task,spawnAgentorresumeAgentcall with no child row classifies as a delegation through the legacy adapter, keeping its row id and title; every new event classifies structurally, by adelegationitem or a child row naming the call. FAQ. - Runtime behaviour a bridge can observe. The recovery hint on every rejection of a request is acted on (the first, each rate-limit ladder rung, the retry after an unarchive), with one unarchive per request; a
provider/recoverynaming a thread the emitting process does not host is dropped. Concepts. - A provider plugin's bundle also loads for its forms and its panel route, not only on the first thread of its provider; boot-time UI belongs in a separate non-provider plugin. Concepts.
- Plugin-declared icons. A manifest map
bb.branding.experimental_icons({ "receipt": "./icons/receipt.svg" }, validated at build and load) lets a row'spresentation.icon, a bb tool's presentation and the provider declaration'siconname your own SVG by its namespaced glyph"<pluginId>/<name>"; the server serves it hashed, refuses an undeclared or foreign name at ingest asprovider/unhandled, and web and mobile draw it tinted with the per-kind glyph as the fallback once the plugin is gone. Conformance rulepresentation/icon-namespaced-declared. Concepts, Reference. - Native roots have two sides.
experimental_nativeSkillRootsandexperimental_nativeCommandRootsare{ user?, project? }, relative to the host's home and the workspace; a declaration that still names anabsoluteside is refused at registration. A directory only one host can name (a config-moved directory, an installed vendor plugin, a config-file entry) is the answer of your host entry'sresolveNativeRoots, which is how every bundled provider, Pi included, now does it; host-daemon protocol 163 carries the two-sided shape. bb scans each absolute path once per provider, the first root in declaration order winning. See Native roots. - Two vendor-plugin readers on
@get-bb/plugin-sdk/host:experimental_resolveClaudePluginRoots(Claude Code's plugin registry, for one workspace) andexperimental_resolveVendorPluginRoots(the walk over plugin directories in theclaudeorgroklayout), for a resolver that lists the plugins installed on a host. Reference. - The bridge kit grew three toolkits on
@get-bb/plugin-sdk/provider-bridge: fourteen maintenance helpers behindprovider/healthandprovider/installation/*for a CLI-backed provider (experimental_resolveExecutablePath,experimental_readCliVersion,experimental_npmGlobalInstallCommand, …), twelve presentation builders (experimental_presentationTitle,experimental_withTitle,experimental_fileReadPresentation, …), and the bounded line reader the daemon itself reads your bridge with (experimental_readBoundedLines). Reference. - The conformance kit assembles for you.
experimental_runBridgeConformance({ transport, session, providerId })takes raw wire messages and runs everythread/deltathrough the real assembler itself;takeMessages()is a method on the captured output;experimental_toConformanceMessagesthrows naming the replacement andCONFORMANCE_ASSEMBLED_EVENT_METHODis retired. The run ends with a release stop, and aninterruptiblePromptInputfixture enables two more rules. Testing. providerThreadIdis required on the result ofthread/start,thread/resumeandthread/fork. The runtime adopts no session without it, athread/identitynotification does not stand in for it, and the rejection names the field. FAQ.experimental_scanPublicSdkOnlyon@get-bb/plugin-sdk/testingis the public-SDK-only scan the echo example used to hand-roll. Testing.- The ACP kit: the probe answers
{ reachable: true, fork }or{ reachable: false, reason }; the launch spec'snativeSkillRootsis{ user, project };AcpAgentProfileis a deprecated alias ofAcpLaunchSpec. Reference. - Archive is a final state.
thread/archiveandthread/unarchiveask for a state, so a bridge whose provider refuses a duplicate answers it as success; the runtime propagates a bridge's rejection verbatim and special-cases no provider's message. Concepts. - Compatibility. A "scheduled removals (next major)" list of eleven unprefixed provider-bridge exports, and the removed
experimental_aiServiceKindSchema,ExperimentalAiJsonValueandExperimentalResolvedNativeRootnames. (This pass also opened a one-release window servingTimelineToolWorkRow.activityIntentsas[]; that window is dropped with the key, see the 2026-08-23 entry above.) Compatibility. - The echo example now writes through
createBridgeIo, runs every handler throughrunBridgeRequest, and builds its rows with the kit's presentation helpers. Quickstart. - Ships as SDK 0.4.16, and
experimental_setThreadRowStatusstays. Public main already published@get-bb/plugin-sdk0.4.15, so every rename, rejection and compatibility window above lands in 0.4.16 (host-daemon protocol stayed at 163 in that pass; it is 164 now, see the entry above); and the content-script mount context'sexperimental_setThreadRowStatusis kept rather than deleted, because its status store feeds the collapsed sidebar section rollup. Compatibility.
What a provider plugin is #
bb runs every conversation on a provider: a coding agent such as Codex, Claude Code, Pi, or any agent that speaks the Agent Client Protocol (Cursor, opencode, Amp, your own). A provider is contributed by a plugin, and nothing else: there is no built-in provider table in bb, so disabling a provider plugin removes its provider from the picker.
A provider plugin is an ordinary bb plugin that does two extra things:
- Its server entry (
server.ts, which runs inside the bb server) callsbb.providers.register({ ... })with a declaration: the provider's id, display name, copy, icon, capabilities, permission modes, reasoning levels, service tiers, fallback models, maintenance support, native skill roots, extension kinds, and a hook that derives per-command options from the plugin's own settings. - Its host entry (
bb.hostin the manifest, bundled bybb plugin build) exports a bridge namedexperimental_providerBridge. The host daemon on an enrolled machine runs that bridge as its own process, and the bridge translates the agent's native protocol into bb's Provider Bridge Protocol.
One plugin may own several providers. The first-party ACP plugin registers one provider per agent it knows about, plus every agent the user configures in the plugin's settings, and all of them run on one shared bridge.
examples/plugins/echo-provider in the bb repository is a full provider plugin in a few files: a declaration, a bridge that answers every prompt by echoing it back, a host RPC entry, a bb tool, plugin settings, two extension kinds, and the tests every provider should ship. The Quickstart walks through it.
Zero first-party privilege #
The bundled provider plugins have no private hooks. They import the same published package a marketplace plugin installs, @get-bb/plugin-sdk and its subpaths, and a test in each of them fails the moment a private @bb/* workspace package is imported. The echo example exists to prove the rule from the outside: it reaches every provider capability through the public SDK alone, and its own public-sdk-only.test.ts guards that.
Two consequences follow. Core never branches on a provider id (no tool-name tables, no per-provider rendering, no special-cased recovery). And anything a first-party provider can do, yours can do too.
The mental model #
thread/delta items with presentation→
delta assembler→
ThreadEvent, persisted→
timeline rows on web, mobile, CLI
- Declaration
- Static facts the server needs before a session exists: what to show in the picker, which permission modes the agent supports, whether it can fork, which sessionless maintenance requests its bridge answers, where it keeps its own skills. The server turns the declaration into one client shape,
ProviderInfo, and never reads anything else about the provider. - Bridge
- A process per provider artifact, spawned by the daemon from your plugin's host bundle. It speaks line-delimited JSON-RPC 2.0 on stdin/stdout: the runtime sends
initialize,thread/start,turn/start,thread/stopand friends; the bridge answers and streamsthread/deltanotifications. The bridge supervises any child processes (the agent's CLI, an SDK session) itself. - Items with presentation
- A bridge never builds timeline events. It emits semantic deltas: a turn opened, a command item opened with its presentation, streamed text, the item closed with its terminal shape, usage, the turn settled. Every
item.openanditem.closecarries a declarativepresentation(label, glyph, title, detail, tint) that is persisted with the row. - Assembly and rendering
- The runtime's delta assembler (the same code the testing kit ships) mints every turn and item id and constructs canonical
ThreadEvents. The server validates extension payloads against the schemas you declared, persists the events, and projects rows. Every client renders every row from the declarative base; a plugin's web bundle may upgrade the body of rows it owns. - Interactions
- When the agent needs the user, the bridge raises an
interaction/request: a closed approval (a command, a file change, a tool use, a permission grant) that the thread's permission mode may auto-decide, or an open request (a user question, a plan review, or a plugin-defined<pluginId>/<name>form) the user answers. - Recovery
- A bridge never makes the runtime match error text. It attaches a typed hint (
sessionArchived,authRequired,restartRecommended,staleTurn,rateLimited) to a rejected request, or sends aprovider/recoverynotification when there is no request to reject. - Skills and native roots
- bb injects its own skills into a session through
skills/configurewhen the bridge's handshake declares support. The declaration also names the directories the agent reads its own skills and slash commands from, relative to the host's home or the workspace, so bb can list them beside its own; a directory only one host can name is the answer of the plugin'sresolveNativeRootshost RPC. - Maintenance
- Sessionless requests the server sends through the bridge when the declaration turns them on:
provider/health,provider/usage,provider/installation/statusandprovider/installation/run. - AI services
- A plugin's host entry can also serve bb's helper inference (thread titles, commit messages) and voice transcription, registered with
bb.experimental_aiServices.register.
Who runs what #
bb server
Owns product policy.
- loads
server.ts, holds the provider registry - defaults, permission modes, tool lists, instructions
- calls
deriveProviderOptionsper command - validates extension payloads at ingest; persists; projects rows
- builds and serves the
bb.hostartifact by digest
(protocol version 164)
host daemon
Host-local primitives on an enrolled machine.
- downloads the artifact, verifies the digest, caches it
- runs the agent runtime and its delta assembler
- spawns one bridge process per provider artifact through a bootstrap
- runs the plugin's host RPC worker (same artifact, separate process)
over stdio (protocol 2, grammar v3)
bridge process (your plugin)
Knows the agent's dialect.
- answers
initialize,thread/*,turn/*,model/list,provider/* - emits
thread/deltawith presentation - raises
item/tool/callandinteraction/request - supervises the agent's own processes
The split is deliberate. The server decides what bb does; the daemon provides where it runs; the bridge knows how the agent talks. The daemon never interprets a provider's native protocol and the bridge never mints a bb id. The Concepts page lists which side owns each decision.
Vocabulary #
| Term | Meaning |
|---|---|
| provider | An agent backend a thread runs on. Identified by a flat, permanent id (codex, acp-cursor, echo-agent). |
| declaration | The object passed to bb.providers.register. Validated at call time, committed when the plugin load commits, replaced wholesale on reload. |
ProviderInfo | The one client shape a declaration projects to. Read by the app's pickers, experimental_useProviders() and bb.sdk.providers.list(). |
| bridge | The module your host artifact exports as experimental_providerBridge; runs as its own process and speaks the Provider Bridge Protocol. |
| host artifact | The self-contained bundle bb plugin build produces from the manifest's bb.host entry (dist/host.js). Carries the bridge export and, optionally, a default-exported host RPC entry. |
| host entry | The default export of bb.host, built with experimental_defineHostEntry: RPC methods the server calls on a specific host (probe an agent, resolve native roots, serve AI services). |
| agent runtime | The daemon-side component that spawns bridges, holds the delta assembler, and talks to the server. |
| delta assembler | Runtime code that turns thread/delta batches into canonical ThreadEvents, minting ids and enforcing the turn and item lifecycle. Shipped to plugins for tests as experimental_createDeltaAssembler. |
| grammar v3 | The current thread/delta vocabulary: core item kinds, presentation, extension items and state, one streaming dialect, one usage dialect. Negotiated at initialize as grammarVersions: [3, 3]. |
| item | One row-producing unit of work inside a turn: a command, a file read, a search, a delegation, a plan snapshot, a tool call, a message, an extension item. |
| presentation | The declarative rendering hint on an item: label { pending, completed }, icon { glyph } (a host glyph, or a plugin-declared icon as <pluginId>/<name>), title?, detail?, suppress?, tint?. |
| extension kind | A plugin-defined item or thread-state kind named <pluginId>/<name>, declared with a schema the server enforces at ingest. |
| interaction | An approval or a request the agent raises mid-turn through interaction/request; every status change appends one system/interaction/lifecycle event. |
| recovery hint | { kind, message, retryable }: the typed reason a request failed or a session needs attention. Carried in error.data.recovery or by provider/recovery. |
| native roots | The directories an agent reads its own skills (experimental_nativeSkillRoots) and slash commands (experimental_nativeCommandRoots) from, declared as { user, project } relative roots; host-absolute ones are resolved per host through resolveNativeRoots. bb lists them beside its own. |
| maintenance | The sessionless requests a declaration turns on: health, usage, installation. |
| AI service | Helper inference or voice transcription a plugin serves from its host entry. |
| conformance kit | The published scenarios that drive a bridge through JSON-RPC hygiene, the handshake and a session lifecycle and report per-rule pass/fail. |
| recording, cell, parity | A capture of both sides of a bridge's wire for one session (a cell such as turn-tools), replayed through the bridge and diffed against itself. |
experimental_ | The prefix every new public plugin API member ships with until it is audited and renamed. See Compatibility. |
Four principles #
These come from the design document the implementation follows, and they explain most of the API's shape.
- Zero first-party privilege. First-party providers use only the public API. Every special case is a public primitive or is deleted.
- Each fact lives in one place. A capability is declared or reported, never both. Pre-session facts live on the declaration; session-behavior facts are reported by the bridge at
initialize. Presentation comes from the bridge, never from core tables. - Core understands a small semantic vocabulary. Everything else is an extension kind with mandatory declarative presentation.
- Every client renders everything without plugin code. Plugin renderers are a web upgrade; mobile renders the declarative base.
Where to go next #
- Quickstart: build the echo provider step by step, run it in bb, write its conformance test.
- Concepts: the protocol, grammar v3 and presentation, interactions, recovery, models and modes, native roots, maintenance, AI services, branding, process lifecycle, and who decides what.
- API reference: every public export of the provider-related SDK subpaths, the declaration field table, the method tables, the ACP kit.
- Testing: the fake plugin host, the conformance kit, recorded cells and parity.
- Compatibility: what
experimental_means and which windows are open. - FAQ: the gotchas.