Skip to content

Delete the dynamic ACP tier and the typed acpLaunchSpec wire field - #2238

Closed
SawyerHood wants to merge 1 commit into
ws2b-acp-6-command-output-fidelityfrom
ws2b-acp-7-delete-the-acp-tier
Closed

Delete the dynamic ACP tier and the typed acpLaunchSpec wire field#2238
SawyerHood wants to merge 1 commit into
ws2b-acp-6-command-output-fidelityfrom
ws2b-acp-7-delete-the-acp-tier

Conversation

@SawyerHood

@SawyerHood SawyerHood commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #2234.

What was wrong

Three owners shared the ACP agents. #2229 and #2231 moved the agents into the plugin; this deletes everything that is left, and with it the last provider-named field on the daemon wire.

The dynamic ACP tier was the reason the typed field existed. acp-<slug> ids from customAcpAgents were never registered, so they had no bridge options of their own — provider-bridge-launch.ts handed them the first registered ACP provider's registration (findAcpTierRegistration), and the typed acpLaunchSpec overrode that provider's spec on the way down. Deleting the field without deleting the tier would have made every user-configured agent silently launch cursor-agent. They land together.

What changed

Server — the tier is gone.

  • services/providers/acp-provider-tier.ts deleted whole. Its exports are all registration reads now: ACP_TIER_CAPABILITIESregistration.info.capabilities, getAcpProviderServerCapabilitiesregistration.serverCapabilities, buildAcpProviderInforegistration.info.
  • provider-registry.ts: the five isAcpProviderId policy fallbacks, the resolveAcpAgentCapabilities dep, and the shared "acp tier" waiter key. That last one was a real bug: a wait for acp-opencode was released by any ACP registration, so a request could proceed against a provider that had not registered. A wait is now released by its own agent's registration and nothing else, and there is a test for it.
  • provider-bridge-launch.ts: the borrow (findAcpTierRegistration, isAcpProviderTierRegistered) and the "not my registration" capability branch.
  • execution-options.ts: the customAcpAgents composition, the two exclusion sets that existed only so a dynamic id could shadow a registered one, and the inline launch spec on provider.list_models.
  • The provider-logo route's config-file branch, the config display-name lookup in routes/threads/data.ts, services/system/acp-launch-spec.ts, and ServerRuntimeConfig.customAcpAgents.

Wire — the typed field is gone from all eight command schemas (the thread runtime context and its resume contexts, turn.submit, thread.goal.clear, and the five sessionless provider commands). hostDaemonAcpLaunchSpecSchema and normalizeHostDaemonAcpLaunchSpec stay: they are the bridge's contract, published through the SDK and used by the kit.

HOST_DAEMON_PROTOCOL_VERSION → 151, with an entry that says what actually breaks: the schemas are strict, so a 150 server's payload is rejected outright by a 151 daemon; the reverse is quiet but wrong, because a 150 daemon's dynamic tier would launch whichever agent its borrowed registration named.

Runtime — two simplifications the deletion makes true.

  • The #acp: process-key suffix is gone. bridgeLaunchProcessKey already fingerprints providerOptions, so two ACP agents on one bridge artifact still get two processes; the suffix was load-bearing only for the tier, where two agents shared one registration's options. Same for the daemon's maintenance-runtime cache. Both have tests that now drive two different providerOptions bags.
  • The NO_ACTIVE_TURN steer recovery drops isAcpProviderId(pid) &&. BRIDGE_JSON_RPC_ERRORS.NO_ACTIVE_TURN is the typed contract — the echo canary and the scripted-echo bridge both raise it — so any bridge that says "that turn is already gone" now gets the stale-steer recovery instead of an error.

One thing needed a replacement, not a deletion. routes/projects.ts read acpLaunchSpec.nativeSkillRoots to tell the host where an agent keeps its own skills. Reaching into a plugin's opaque bag for that would violate the rule the bag exists for, so it is now a declared capability: experimental_nativeSkillRoots on PluginProviderDeclaration (validated: relative paths, no dot segments, ≤32 per side), projected onto the registration, read by the route. The ACP plugin fills it from each agent definition. Entry added to docs/api_to_audit.md.

How I verified

gate result
typecheck (whole repo) 75/75
test --filter=@bb/host-daemon-contract 3 files, 52 tests — including the round-trip test, rewritten to carry the spec in bridgeLaunch.providerOptions, and the optional-field allowlist with its 48 acpLaunchSpec.* entries removed
test --filter=@bb/agent-runtime --force 31 files, 439 tests
test --filter=@bb/host-daemon --force 46 files, 555 tests
test --filter=@bb/server --force 1892 passed, 1 failed — the failure is internal-skill-trees expecting file mode 0644 and getting 0664, a pre-existing failure on this machine's umask 0002, unrelated to this change
test --filter=bb-plugin-provider-acp 5 files, 36 tests
G1 provider-literal ratchet 148 → 128 references, 40 → 37 files; baseline regenerated with --write

Roughly fifteen test files moved off the tier. Where the behavior still exists, the test was rewritten onto the registered path rather than deleted — a configured ACP agent is registered the way the plugin registers one (a new extraProviders harness option and a registerConfiguredAcpProvider helper), so public-thread-fork, thread-runtime-config, public-project-commands, execution-options and the dynamic-acp-agent integration smoke still exercise a user-configured agent end to end. Only tests whose subject was the tier itself are gone (the config-wins-over-plugin precedence, the "no ACP plugin registered" borrow, and the two ServerRuntimeConfig.customAcpAgents refresh cases). provider-logo.test.ts is rewritten onto the registration icon path — which needed the test harness to capture icon bytes the way the plugin runtime does, so the route's 200 path is covered for the first time.

Left for a follow-up, deliberately

  • isAcpProviderId survives in exactly one place: runtime-skill-roots.ts, where the "acp" skill-root flavour is a shape tag (a directory plus an inline manifest, because an ACP agent cannot discover skills from a directory) misnamed as a provider id. Retiring it needs a declared skill-root-shape capability from the bridge handshake, not a rename. Both the function and the matcher now carry a comment saying so.
  • apps/app/src/lib/provider-icon.ts still keys ACP brand marks and tints on the id prefix. ProviderInfo.family (declared since Let the ACP plugin own its agents, including the user's own #2229) and strings.iconTint can retire most of it, but the vendored React marks must keep beating logoUrl — an <img> is a separate document where currentColor is black, invisible on dark themes — so it is its own change.
  • provider-model-catalog.ts still hardcodes acp-* as host-scoped. That one is genuinely blocked on new contract surface (a declared modelCatalogScope), and a wrong answer costs a redundant probe rather than correctness.

AGENT GENERATED: by Claude Opus 5

The ACP agents had three owners: the plugin declared five providers, the
server composed user-configured agents from customAcpAgents at request time,
and the ACP tier answered capabilities for anything acp-*. The previous
layers moved all of it into the plugin. This deletes what is left.

Gone from the server: acp-provider-tier.ts, the five isAcpProviderId
fallbacks on the registry, the tier's capability resolver, the borrow in
provider-bridge-launch (an unregistered acp-* id ran on whichever ACP
plugin registration came first), the customAcpAgents composition in
execution-options, the config-logo branch of the provider-logo route, the
config display-name lookup, and ServerRuntimeConfig.customAcpAgents.

Gone from the wire: the typed acpLaunchSpec field on eight command schemas.
An ACP agent's launch spec now reaches its bridge inside
bridgeLaunch.providerOptions, the opaque bag every provider's static options
already use — the bridge has always read it from there, so the daemon→bridge
hop is unchanged. HOST_DAEMON_PROTOCOL_VERSION goes to 151 with a truthful
entry: the schemas are strict, so an old server's payload is rejected
outright by a new daemon.

The #acp: process-key suffix goes with it: the bridge key already
fingerprints the launch's provider options, so two agents on one artifact
still get two processes. The NO_ACTIVE_TURN steer recovery drops its
provider-id guard and keys on the typed error code, which every bridge can
raise. A provider's own skill roots become a declared capability
(experimental_nativeSkillRoots) instead of a field the server dug out of an
ACP launch spec.

G1 provider-literal ratchet: 148 -> 128 references, 40 -> 37 files.

Co-Authored-By: Claude <noreply@anthropic.com>
@SawyerHood
SawyerHood force-pushed the ws2b-acp-7-delete-the-acp-tier branch from 745f8ec to ba1843e Compare August 21, 2026 18:17
@SawyerHood SawyerHood closed this Aug 21, 2026
@SawyerHood
SawyerHood deleted the ws2b-acp-7-delete-the-acp-tier branch August 21, 2026 21:41
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