Protocol revisions and deprecation windows.
Your agent platform now has a dependency that expires on somebody else's calendar and sits on both ends of a connection you only own one of: the protocol revision. Unlike a model deprecation — one vendor, one date, one cutover you control — you cannot upgrade a protocol, because the other end upgrades when it feels like it. What you can do is run two revisions at once on purpose, and the operational unit that makes that survivable is a number almost nobody records: the distribution of protocol revisions in your live traffic.
This is not model deprecation, and the difference is who moves.
A model deprecation is unpleasant but tractable: a vendor announces a date, you re-run your evals against the successor, you cut over, and every caller of your service comes along because they were never talking to the model directly. The whole migration sits inside your deployment boundary. That is the subject of model deprecation and migration, and its playbook does not transfer here.
A protocol deprecation is a two-party problem. If you run a tool server, your clients are agent hosts built by other people on their own release trains, some of them shipped into environments that update once a quarter. If you run an agent host, your servers are third-party deployments you cannot pin. Either way, exactly one end is yours.
- You cannot force the other end. The nearest thing to force is a hard error at a date you announce, which converts a compatibility problem into an outage you caused.
- The change is often not additive. Recent protocol work in this ecosystem has removed handshakes, retired headers, and deprecated whole feature families — these are not fields you can safely ignore.
- Your library is a second calendar. The revision your SDK implements and the revision the spec publishes are different facts, and the gap between them is frequently months. See third-party tool drift for the sibling problem one layer up.
The instinct that fails here is treating a revision bump as a dependency upgrade — bump the version, run the tests, ship. That works when the dependency is entirely inside your process. A protocol lives in the gap between two processes, and the tests you own only cover your side of it.
The number you do not have: revisions by client, in production.
Ask an operator which protocol revision their tool server speaks and you will get an answer. Ask which revisions their callers speak, in what proportions, and you will usually get a shrug. That second number is the entire input to every decision in this page, and it is nearly free to collect, because the revision travels in the request.
- Record the negotiated revision on every request, alongside the client identifier and the SDK's user-agent. One low-cardinality label on the metric you already emit.
- Report it as a share of traffic and as a count of distinct callers. These diverge hard: 0.3% of requests can be 40% of your integrations, and the second number is the one that generates support tickets.
- Break out the tail by identity, not by volume. The client stuck two revisions back is usually one important customer with a frozen deployment, and you want their name before the cutover meeting, not after it.
- Alert on first sighting of a new revision. Clients upgrade before you notice; the first request at a revision you have not tested is the cheapest possible warning.
Without this, every deprecation decision is made on vibes, and the failure mode is uniform: teams either hold the old revision forever because they cannot prove nobody uses it, or drop it and discover the users in the incident channel. Wiring is covered in tracing and observability.
Serve both revisions from one deployment, not two.
The obvious answer to a dual-revision window is two deployments: legacy endpoint, current endpoint, DNS split. It is also the expensive answer, because it forks the thing you actually care about — your tool implementations, your authorisation checks, your rate limits — and the fork lasts as long as the window does, which is longer than anyone plans.
Prefer a single deployment that negotiates per connection and normalises inward: one internal representation of a call, with the revision handled at the edge.
- Negotiate per connection, not per deployment. This is a property to look for when choosing a framework — some handle it for you and some make it your problem.
- Normalise at the boundary. Translate both revisions into one internal request shape immediately. If a revision-specific branch reaches your business logic, you have two servers wearing one binary.
- Make the shim deletable. Keep the compatibility layer in one module with a removal date in the file, so retiring a revision is deleting a directory rather than an archaeology exercise.
- Fail loudly and specifically. When you do drop a revision, the error must name the revision, the replacement, and a link — not a 400. Half your remaining laggards are unattended jobs whose owner will read exactly one log line.
Feature deprecations bite before revision deprecations do.
Teams watch the revision number and get ambushed by the features inside it. A protocol can hold a revision stable while marking individual capabilities deprecated with their own clocks — typically a published minimum window, often around a year, running from the release that announced it. Those clocks expire while your revision is still perfectly current.
Track them separately:
- Inventory which deprecated capabilities you actually invoke, from traces rather than from code review — the ones you use by accident, through a framework default, are exactly the ones grep will not find.
- Diff each release note against that inventory. The expensive deprecations are the quiet ones: a transport marked legacy, a capability moved out of the core and into an optional extension.
- An extension is not a guarantee. When a feature moves from the specification into an extension, its availability becomes per-implementation. Code that assumed it was always there now needs a capability check and a fallback; see graceful degradation and fallback.
- Put the dates where expiry dates already live. Whatever calendar holds your certificate and key rotations should hold these. A deprecation with no owner and no date is a deprecation you will meet during an incident.
Keep a pinned-old client in CI, on purpose.
Every test you have runs your server against a client from the same commit, which is the one combination production never contains. The regression you ship is compatibility, and it is invisible to a same-version test suite.
- Pin one client at the oldest revision you still support and run the full tool suite against it on every build. When that job goes red, you have found the drop date rather than caused an incident.
- Pin one at the newest revision, including pre-release. This is your early warning for the next window, and it costs one more job.
- Assert on the negotiation, not just the result. A client that silently downgrades will pass your functional tests while quietly telling you nothing about the new revision.
- Keep the fixtures in the repository. Recorded request/response pairs per revision outlive the SDK versions that produced them, and they are what you diff when a vendor claims backwards compatibility.
This belongs with the rest of your fidelity tiers — see staging environments for agents.
Choose libraries on their revision lag, not their benchmarks.
The published comparisons of protocol libraries are about throughput and memory, and for a server whose work is calling somebody else's API those numbers are dominated by the upstream round trip — a difference of a couple of milliseconds inside a response that takes hundreds. The number that will actually cost you a quarter is how long the library takes to implement a new revision, and whether it is the implementation the specification's maintainers track.
- Measure lag in days, historically. For each of the last three revisions: spec publication date, first release implementing it, first stable release. Three data points predict the fourth better than any roadmap does.
- Check whether the popular library is the tracked one. In more than one language the community favourite and the officially maintained implementation are different projects at different revisions — popularity is not currency, and switching is a rewrite rather than an upgrade.
- Prefer explicit multi-revision support to a promise. "Backwards compatible with older revisions" in a README is worth more than a roadmap item, and it is testable today with the pinned client from Step 5.
- Count the migration, not the port. The cost of moving libraries is not rewriting the handlers; it is re-establishing the auth integration, the tracing, the rate limits and the tests around them.
Do two things this week and the rest of this page becomes routine. First, stamp the negotiated protocol revision on every request as a label and put a panel next to your error rate showing revisions by share of traffic and by distinct caller — you cannot make a single decision here without it. Second, add one CI job that runs your suite against a client pinned to the oldest revision you claim to support; the day it goes red is a date you chose rather than one you were handed. After that, keep deprecated-capability dates on the same calendar as certificate expiry, and re-check your library's historical revision lag before the next renewal. Related: rollout and versioning for how the shim ships, and MCP ops in production for the protocol most of this will bite you on first.