Skip to content

Let the ACP plugin own its agents, including the user's own - #2229

Closed
SawyerHood wants to merge 1 commit into
ws2b-acp-4-publish-the-kitfrom
ws2b-acp-5-plugin-owns-agents
Closed

Let the ACP plugin own its agents, including the user's own#2229
SawyerHood wants to merge 1 commit into
ws2b-acp-4-publish-the-kitfrom
ws2b-acp-5-plugin-owns-agents

Conversation

@SawyerHood

@SawyerHood SawyerHood commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #2228.

What was wrong

Three owners shared bb's ACP agents:

  1. plugins/provider-acp/server.ts declared five providers in a hardcoded array.
  2. The server composed user-configured agents from a customAcpAgents array in ~/.bb/config.json, at request time, in execution-options.ts.
  3. The ACP tier (acp-provider-tier.ts) supplied capabilities for any id starting with acp-.

So a user could add an agent only by hand-editing bb's own config file (no CLI surface, needing bb-app config refresh), only bb could add a known one, and every agent inherited one tier answer: fork: "tip" for all five, supportsManualCompaction from a config field the tier read back out.

What changed

One shape, one path. src/agents.ts defines AcpAgentDefinition — a launch spec plus the facts a declaration needs — and src/declaration.ts turns any definition into a PluginProviderDeclaration. src/known-agents.ts holds the five bb ships. A user-configured agent is the same shape and the same path; nothing about the built-ins is privileged.

Configured agents are the plugin's own setting. customAgents holds a JSON array, so the CLI already reaches it:

bb plugin config provider-acp set customAgents '[{"id":"amp","displayName":"Amp","command":"amp","args":["acp"]}]'

settings.onChange disposes the previous registrations and registers the new set, so an edit applies with no restart and no config refresh. Every rejected entry is logged with the reason (malformed, shadows a built-in id, configured twice) — an agent that vanishes silently is a support ticket.

Each agent declares its own facts. fork, dialect, reasoningLevels, supportsManualCompaction, visibility, usage and installation support are per agent instead of per tier. Every registration carries experimental_family: "acp", which is what groups the agents once the acp- prefix stops meaning anything (layer 6). The acp-<slug> id stays exactly as it is: threads persist provider ids, so they can never change.

The deprecated config array is read, never written. src/legacy-config.ts reads customAcpAgents from <dataDir>/config.json, registers what it finds, and logs a deprecation notice per agent naming the setting to move it to. A setting entry wins over a config entry with the same id. The module is self-contained and dated: it goes away in 0.40.

Deliberate behavior changes

  • A user-configured agent now declares fork: "none" instead of inheriting the tier's "tip". The bridge refuses session/fork for an agent that never advertised it, but only after bb created the fork thread, so a declaration above what the agent answers is a thread that dies on start (acp-cursor advertises fork support it does not have; forking births an errored thread #1833) — and bb has never seen a configured agent's initialize reply. The five known agents keep exactly the values they declare today, including the unverified "tip" on opencode/omp/hermes-agent, marked as guesses in the source. Q21's per-instance probe replaces every guess with what the agent answers.
  • A configured agent's logo is no longer used. The legacy field pointed at an arbitrary file the server served from a route of its own; a plugin-registered provider's icon is a host glyph or an asset the plugin ships. The field still parses (an old config must not break) and the agent shows the generic tool glyph. This is a real, small loss and I did not want to hide it.
  • A configured agent's sign-in copy is honest. With no signInCommand, the hint says "Sign in to Amp on the machine, then reload." instead of naming a command bb invented.

How I verified

  • pnpm exec turbo run typecheck (whole repo): 75/75.
  • test --filter=bb-plugin-provider-acp: 4 files, 27 tests. New coverage: the parser keeps a well-formed agent and defaults what it omitted; it reports a malformed entry, a shadowed built-in and a duplicate; the deprecated logo parses and yields the glyph; the launch spec drops a modelCli with nothing to list; every known agent declares family acp; Cursor and grok declare fork: "none" and their dialects while opencode keeps "tip"; grok keeps its own three-level reasoning ladder; a configured agent gets honest copy. legacy-config.test.ts covers data-dir resolution (BB_DATA_DIR, ~/…, the production default), reading the array, a missing file and an empty array being normal, and unreadable JSON being reported rather than thrown.
  • apps/server first-party-provider-plugins.test.ts — the test that pins every built-in provider's client-read declaration — passes unchanged, which is the check that this layer preserved the five known agents' behavior.
  • Full @bb/server suite: 1895 passed, 2 failed. One is this test before I restored the unverified fork values (now passing). The other is internal-skill-trees.test.ts expecting file mode 0644 and getting 0664 — a pre-existing local failure on this machine's umask 0002, unrelated to this change.
  • node scripts/check-provider-literal-ratchet.mjs: OK, 148 across 40 core files — unchanged.

Doc surfaces updated in the same change, per AGENTS.md: docs/configuration.md (rewritten "Custom ACP Agents" with the new CLI command and a dated deprecation subsection), packages/templates/src/templates/bb-guide-providers.md (the in-CLI guide), and the bb-cli builtin skill.

What this layer does not do yet

The server's ACP tier still exists, so a legacy config entry still wins over the plugin's registration for the same id (the server's own precedence rule) and unregistered acp-* ids still fall back to tier capabilities. Layer 6 deletes the tier, after which the plugin is the only source. Q21's per-instance capability probing is the next layer.

AGENT GENERATED: by Claude Opus 5

The ACP agents were split across three owners: the plugin declared five
providers in a hardcoded list, the server composed user-configured agents
from a customAcpAgents array in config.json at request time, and the ACP tier
supplied capabilities for anything acp-*. A user could add an agent only by
hand-editing bb's config file, and only bb could add a known one.

Now the plugin owns both. A known agent and a configured agent are the same
AcpAgentDefinition and become a declaration the same way. Configured agents
live in the plugin's own customAgents setting, so bb plugin config sets them
and a change re-registers the providers with no restart. Each agent carries
its own fork support, dialect, reasoning ladder and compaction support
instead of inheriting one tier's answer for five agents, and every one of
them declares family "acp" so nothing has to parse an id prefix.

The deprecated customAcpAgents array is still READ, never written: each agent
it declares is registered and logged with a deprecation notice naming the
setting to move it to, until 0.40. A setting entry wins over a config entry
with the same id.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant