THIS EXPLANATION
THE ROOM
CDA·28 Computing, Data & AI 6 MIN · 8 STATIONS

Capability negotiation

A Socratic walk-through of capability negotiation — reasoned out one step at a time, not lectured.

abcdefgh
a

The question we started with

THE QUESTION #

Why do two programs that have never met agree on what they can do instead of checking a version number?

Two programs written by strangers, compiled years apart, connect for the first time. Before they can do anything useful they must establish what the other is capable of. The obvious move is to exchange version numbers: "I speak version 3", "so do I", proceed. It is compact, it is cheap, and almost every long-lived protocol that tried it eventually stopped.

So the question is not what would work — version numbers do work, for a while — but what breaks, and why the fix takes the particular shape of each side announcing a list of things it can do.

b

Reasoning it through

REASONING #

Ask first what a version number actually asserts. "Version 3" is a claim about a bundle: it says every feature in the version-3 specification is present, and none of the version-4 ones. That is a strong claim, and it is true only if implementations advance in lockstep. Do they?

Consider what happens the moment they do not. A server implements most of version 4 but not one optional part of it. What number should it send? Say 4, and a client will invoke the missing part and fail. Say 3, and the client will never use any of the version-4 features the server does have, including the ones it needs. There is no honest answer, because the question has one dimension and reality has many. That is the crux: a version number is a single ordinal standing in for a set.

Now suppose instead each side sends the set itself — not "version 4" but "I can do subscriptions, I can do change notifications, I cannot do sampling". What can the other side compute from that? The intersection. Each party's own list is something it knows for certain; the intersection is precisely the set of interactions where both parties are confident, and everything outside it is simply never attempted. Notice that no one had to agree in advance on what the whole world supports — the agreement is constructed at connection time from two local facts.

There is a second property, easy to miss. Version numbers are ordered, so they imply that later means more. Capabilities are not ordered. A tiny embedded client and a full desktop client are not on a line; they overlap in some places and differ in others. A set-valued declaration can express that; an integer cannot.

Does this mean version numbers disappear? No, and it is worth being precise here rather than tidy. Real protocols usually keep a version for the frame and negotiate capabilities for the contents. The version pins the wire format — how a message is encoded, what fields the handshake itself has — because two parties who disagree about that cannot exchange capability lists at all. You need a shared language before you can compare vocabularies. The Model Context Protocol does exactly this: the opening initialize exchange carries both a protocol version and a structured capabilities object, and the client then sends an initialized notification once the terms are settled. TLS does the same, agreeing on a protocol version and then selecting a cipher suite from what both sides offer. SMTP's EHLO and IMAP's CAPABILITY are older instances of the same instinct: ask the server to list its extensions rather than infer them from a release.

One more question worth sitting with: why must this be an exchange rather than a probe? A client could just try a feature and see whether it errors. But then every unsupported feature costs a round trip and produces a failure that is indistinguishable from a real one — did the call fail because the feature is absent, or because it broke? Declaring up front converts a runtime error into a design-time fact, and that is most of the value.

c

The analogy

THE ANALOGY #
THE FIGURE

Think of two people meeting who share no common language with certainty. The version-number approach is to compare passports and assume that citizenship determines fluency. The capability approach is for each to say plainly: "I speak Portuguese and some Italian." Neither had to know what the other would say; the moment both lists are on the table, the usable set is whatever appears in both, and any attempt to speak outside it is dropped before it is tried.

WHERE IT BREAKS DOWN

speakers can muddle through a half-shared language and be partly understood, whereas a protocol feature is binary — there is no fractional support, and a capability declared is a promise the implementation must keep completely.

d

Clarifying the model

THE MODEL #

A few refinements keep this from being over-applied.

A capability declaration is a promise, not a hint. If a server announces a capability it does not fully implement, the client has no fallback path — it has already been told not to worry. This makes over-declaring the more dangerous error by far, and it is why capability names should be narrow enough to be honestly answerable. A capability called "advanced features" is useless precisely because no one can say truthfully whether they have it.

Nor does negotiation eliminate versioning; it relocates it. Something still has to be versioned — the handshake structure itself, and the meaning of each capability name. If the semantics of a capability change silently, both sides still agree, and both are now wrong. So a capability, once published, is effectively immutable: you add a new name rather than redefine an old one. That is the real discipline the technique demands.

Finally, the intersection is computed per connection, not per release. Two long-lived peers can produce different agreed sets in the morning and the afternoon if one restarts with a plugin disabled. This is a feature — it lets deployment configuration, not just software version, determine what is on the table — but it means neither side may cache the negotiated set beyond the session that produced it.

e

A picture of it

THE PICTURE #
Capability negotiation
Capability negotiation read top to bottom as time. The first two arrows are the only place a version appears, and it governs the envelope, not the features. The notes mark the moment each side computes the same intersection from two different local facts. The final crossed arrow is the payoff: an unsupported call is not attempted and never becomes an error to diagnose. {"generator":"[email protected]","source":"../Socrates/.diagram-cache/_src/capability-negotiation.md","sourceIndex":1,"sourceLine":4,"sourceHash":"54dc1c8a9564e7744f837e290f089b3c74765773256dddbf85227658b3e3f489","diagramType":"sequence","layoutVariant":"source","repairedDuplicateIds":[],"motion":"entrance-with-reduced-motion-fallback","presentation":"editorial","attempt":1,"viewBox":{"x":0,"y":0,"width":808,"height":776},"qa":{"passed":true,"findings":[]}} Server 01 Client 02 The version pins the wire format only Each side computes the intersection locally Never sent -- ruled out at handshake, not at runtime initialize -- protocol version plus my capability set 1 my protocol version plus my capability set 2 initialized -- terms are settled 3 call a capability present in both sets 4 result 5 call a capability absent from the server set 6
KINDSlifelineparticipantmessage

How to readread top to bottom as time. The first two arrows are the only place a version appears, and it governs the envelope, not the features. The notes mark the moment each side computes the same intersection from two different local facts. The final crossed arrow is the payoff: an unsupported call is not attempted and never becomes an error to diagnose.

f

What became clearer

WHAT CLEARED #
WHAT CLEARED

A version number compresses a set of abilities into a single ordinal, and that compression is lossy the moment implementations stop advancing together. Declaring capabilities restores the set, letting two strangers derive their shared ground at connection time from facts each already knows about itself — with a version kept only for the envelope both must agree on before any of that can be said at all.

g

Where to go next

ONWARD #
  • How a protocol retires a capability that turned out to be a mistake, without breaking peers that still declare it.
  • Why feature detection won in web browsers over user-agent sniffing, which is the same argument in a different setting.
  • What changes when there are three parties rather than two, and the intersection has to hold across a chain.
h

Key terms

TERMS #
TermWhat it means
Handshakethe opening exchange in which two parties establish the terms of everything that follows.
Capabilitya named, independently supported behaviour that a party declares it implements in full.
Intersection semanticsthe rule that only capabilities present in both declarations may be used.
Protocol versiona number governing the message format and handshake structure itself, distinct from the features carried inside it.
Feature detectiondeciding what to use by asking whether a specific ability is present, rather than inferring it from an identity or release.

Every term the collection defines is gathered in the glossary.

Nearby on the shelf

4