Delete the dynamic ACP tier and the typed acpLaunchSpec wire field - #2238
Closed
SawyerHood wants to merge 1 commit into
Closed
Delete the dynamic ACP tier and the typed acpLaunchSpec wire field#2238SawyerHood wants to merge 1 commit into
SawyerHood wants to merge 1 commit into
Conversation
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
force-pushed
the
ws2b-acp-7-delete-the-acp-tier
branch
from
August 21, 2026 18:17
745f8ec to
ba1843e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 fromcustomAcpAgentswere never registered, so they had no bridge options of their own —provider-bridge-launch.tshanded them the first registered ACP provider's registration (findAcpTierRegistration), and the typedacpLaunchSpecoverrode that provider's spec on the way down. Deleting the field without deleting the tier would have made every user-configured agent silently launchcursor-agent. They land together.What changed
Server — the tier is gone.
services/providers/acp-provider-tier.tsdeleted whole. Its exports are all registration reads now:ACP_TIER_CAPABILITIES→registration.info.capabilities,getAcpProviderServerCapabilities→registration.serverCapabilities,buildAcpProviderInfo→registration.info.provider-registry.ts: the fiveisAcpProviderIdpolicy fallbacks, theresolveAcpAgentCapabilitiesdep, and the shared"acp tier"waiter key. That last one was a real bug: a wait foracp-opencodewas 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: thecustomAcpAgentscomposition, the two exclusion sets that existed only so a dynamic id could shadow a registered one, and the inline launch spec onprovider.list_models.routes/threads/data.ts,services/system/acp-launch-spec.ts, andServerRuntimeConfig.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).hostDaemonAcpLaunchSpecSchemaandnormalizeHostDaemonAcpLaunchSpecstay: 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.
#acp:process-key suffix is gone.bridgeLaunchProcessKeyalready fingerprintsproviderOptions, 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 differentproviderOptionsbags.NO_ACTIVE_TURNsteer recovery dropsisAcpProviderId(pid) &&.BRIDGE_JSON_RPC_ERRORS.NO_ACTIVE_TURNis 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.tsreadacpLaunchSpec.nativeSkillRootsto 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_nativeSkillRootsonPluginProviderDeclaration(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 todocs/api_to_audit.md.How I verified
typecheck(whole repo)test --filter=@bb/host-daemon-contractbridgeLaunch.providerOptions, and the optional-field allowlist with its 48acpLaunchSpec.*entries removedtest --filter=@bb/agent-runtime --forcetest --filter=@bb/host-daemon --forcetest --filter=@bb/server --forceinternal-skill-treesexpecting file mode0644and getting0664, a pre-existing failure on this machine'sumask 0002, unrelated to this changetest --filter=bb-plugin-provider-acp--writeRoughly 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
extraProvidersharness option and aregisterConfiguredAcpProviderhelper), sopublic-thread-fork,thread-runtime-config,public-project-commands,execution-optionsand thedynamic-acp-agentintegration 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 twoServerRuntimeConfig.customAcpAgentsrefresh cases).provider-logo.test.tsis 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
isAcpProviderIdsurvives 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.tsstill 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) andstrings.iconTintcan retire most of it, but the vendored React marks must keep beatinglogoUrl— an<img>is a separate document wherecurrentColoris black, invisible on dark themes — so it is its own change.provider-model-catalog.tsstill hardcodesacp-*as host-scoped. That one is genuinely blocked on new contract surface (a declaredmodelCatalogScope), and a wrong answer costs a redundant probe rather than correctness.