Skip to content

fix(conductor)!: narrow the conductor API and fix multi-version chains - #475

Merged
benbrandt merged 5 commits into
mainfrom
cleanup-conductor
Nov 1, 2026
Merged

benbrandt merged 5 commits into
mainfrom
cleanup-conductor

Conversation

@benbrandt

@benbrandt benbrandt commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Pre-release (5.0) cleanup of the conductor, polyfill and trace viewer crates.

Addressed

  • F12: explicit conductor re-exports; ConductorRunner, ConductorMessage, ConductorSuccessor, ComponentIndex, SourceComponentIndex are no longer public. ConductorHostRole is a sealed marker trait. Its methods and Instantiator moved to a private supertrait, and each method takes a crate-private Token, so code outside the crate cannot call them through a ConductorHostRole bound. ConductorImpl::new is private.
  • F13: #[non_exhaustive] on ConductorArgs, ConductorCommand and its variants.
  • F44: instantiators return #[non_exhaustive] InstantiatedChain / InstantiatedProxies instead of tuples. ProxiesAndAgentComponents is removed.
  • F16: trace::Protocol is renamed to trace::MessageProtocol.
  • F02: SelectedInitializeRequest::into_v1() negotiates a v2 request down to v1, and the conductor accepts an instantiator request at or below the selected version. A v1-only instantiator now serves v2 clients with v1 instead of failing the connection permanently.
  • F34: successor_of now returns Agent for the last proxy, so traces include last-proxy-to-agent traffic. Snapshots are regenerated.
  • F59: the error text says proxy/initialize instead of initialize/proxy.
  • G79: dropped the dev-only conductor-to-polyfill unstable_protocol_v2 edge, and fixed the docs: a v1-only polyfill negotiates the chain down to v1, it does not reject.
  • G80: the polyfill rewrites session/fork in every build and only validates it typed with unstable_session_fork.
  • F33: the polyfill answers an invalid or version-changing proxy/initialize with an error instead of closing its connection, and forwards a repeat on the same version. The mismatch error names the versions as v1/v2. Regression tests cover a valid and a malformed repeated initialize.
  • F45: #[non_exhaustive] on McpOverAcpPolyfill and TraceViewerConfig. TraceViewerConfig gets port / open_browser setters.
  • F46 (small part only): TraceSource is private because it appeared in no public signature.
  • G81: docs.rs metadata for the conductor and polyfill.

md/migration-v5.md (conductor section, including the polyfill and trace behavior changes), md/conductor.md, md/protocol-v2.md, md/mcp-bridge.md and the polyfill README are updated.

Skipped / partial

  • F46: I left the rest of the trace-viewer API as it was. serve_memory still returns anyhow::Result even though it can't fail, and VIEWER_HTML is still public. Neither blocks additive changes later.

Notes

  • After G79, the conductor's tests/mcp_over_acp_polyfill_v2.rs needs the polyfill's own v2 feature. Its required-features include agent-client-protocol-polyfill/unstable_protocol_v2, so cargo test -p agent-client-protocol-conductor --features unstable_protocol_v2 skips the test instead of failing it.
  • The polyfill's v1/v2 connection negotiates proxy/initialize before the polyfill handler sees it, so through a conductor a malformed version is answered with v1. The handler's error path for invalid or mismatched requests is a defensive fallback.

Co-Authored-By: Claude noreply@anthropic.com

benbrandt and others added 5 commits November 1, 2026 21:11
…nitialize

- Treat `session/fork` as a session setup method in every build. Without
  `unstable_session_fork` the polyfill used to forward `McpServer::Acp`
  entries of forked sessions unchanged; now it rewrites them and leaves
  validation to the agent (G80).
- Answer an invalid or version-changing `proxy/initialize` with an error
  instead of closing the polyfill's connection, and forward a repeated
  `proxy/initialize` on the same version (F33).
- Drop the conductor's `unstable_protocol_v2` -> polyfill feature edge. It
  only applied to the conductor's dev-dependency, so real users got a v1-only
  polyfill anyway. Document that the polyfill needs its own feature and
  negotiates the chain down to v1 without it (G79).
- Mark `McpOverAcpPolyfill` `#[non_exhaustive]` (F45).
- Add docs.rs metadata to the conductor and polyfill crates (G81).

BREAKING CHANGE: `McpOverAcpPolyfill` is `#[non_exhaustive]`; create it with
`McpOverAcpPolyfill::http()`. The conductor's `unstable_protocol_v2` feature no
longer enables `agent-client-protocol-polyfill/unstable_protocol_v2`.

Co-Authored-By: Claude <noreply@anthropic.com>
…aceSource

`TraceViewerConfig` gains `port` and `open_browser` setters and is
`#[non_exhaustive]`, so new options can be added later (F45). `TraceSource`
appeared in no public signature, so it is now private (F46).

BREAKING CHANGE: build `TraceViewerConfig` from `TraceViewerConfig::default()`
with the setters instead of a struct literal. `TraceSource` is no longer
public.

Co-Authored-By: Claude <noreply@anthropic.com>
- Replace the `pub use conductor::*` glob with explicit re-exports.
  `ConductorRunner`, `ConductorMessage`, `ConductorSuccessor`,
  `ComponentIndex` and `SourceComponentIndex` are no longer public.
  `ConductorHostRole` becomes a sealed marker trait; its `Instantiator`
  type and methods move to a private supertrait, and `ConductorImpl::new`
  is private (F12).
- Mark `ConductorArgs`, `ConductorCommand` and its variants
  `#[non_exhaustive]` (F13).
- Instantiators return `#[non_exhaustive]` `InstantiatedChain` /
  `InstantiatedProxies` structs instead of tuples, and the
  `ProxiesAndAgentComponents` alias is removed (F44).
- Rename `trace::Protocol` to `trace::MessageProtocol` (F16).
- `SelectedInitializeRequest::into_v1` negotiates a v2 request down to v1,
  and the conductor accepts an instantiator's request at or below the
  selected version. A v1-only instantiator no longer rejects v2 clients and
  leaves the connection failed (F02).
- Fix `ComponentIndex::successor_of`, which never returned `Agent`, so
  traces now include traffic between the last proxy and the agent (F34).
- Name `proxy/initialize` correctly in the outside-initialize error (F59).

BREAKING CHANGE: instantiators return `InstantiatedChain::new(request,
proxies, agent)` / `InstantiatedProxies::new(request, proxies)` instead of
tuples; conductor routing internals are no longer exported;
`ConductorHostRole` has no public items; `ConductorImpl::new` is private;
`ConductorArgs`/`ConductorCommand` are `#[non_exhaustive]`;
`trace::Protocol` is renamed to `trace::MessageProtocol`; `into_v1()`
downgrades a v2 request instead of failing.

Co-Authored-By: Claude <noreply@anthropic.com>
`ConductorHostRole` hid its methods on a private supertrait, but code
outside the crate could still call them through a `ConductorHostRole`
bound, since a bound's supertrait methods need no import. That kept
`handle_dispatch` and its `futures` mpsc sender in the public API.

Each sealed method now takes a `Token` that only the conductor crate can
create, so outside code can no longer call them.

Co-Authored-By: Claude <noreply@anthropic.com>
…or changes

- Format the polyfill's protocol as "v1"/"v2" in the version-mismatch
  error instead of `Some(V1)`.
- Test that a malformed repeated `initialize` keeps the polyfill's
  connection open.
- Gate the conductor's v2 polyfill test on the polyfill's own
  `unstable_protocol_v2` feature with `required-features`, so running the
  conductor tests with only its own feature skips the test instead of
  failing it.
- List the polyfill and trace behavior changes in the migration guide.

Co-Authored-By: Claude <noreply@anthropic.com>
@benbrandt
benbrandt merged commit b64813e into main Nov 1, 2026
14 checks passed
@benbrandt
benbrandt deleted the cleanup-conductor branch November 1, 2026 20:33
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