We shipped a specification with a rule that made it unusable for the single thing almost everyone would want to do with it. It took an HTTP 400 to notice.
Here is the error, from a request built directly out of a conformant manifest:
HTTP 400
{"error":{"code":"invalid_request_error",
"message":"tool function names must match ^[a-zA-Z0-9_-]{1,64}$",
"param":"tools[0].function.name"}}
Nothing was malformed. The manifest validated. The capability name was exactly what the specification required it to be. That was the problem.
Two rules that cannot both be satisfied
The Agent Discovery Specification defines capability names as namespacedName:
^[A-Za-z][A-Za-z0-9-]{0,63}(\.[A-Za-z][A-Za-z0-9-]{0,63})+$
The trailing + is the whole story. It makes at least one dot mandatory.
com.example.lookupOrder is conformant. lookupOrder is not.
OpenAI's Chat Completions API constrains tools[].function.name to:
^[a-zA-Z0-9_-]{1,64}$
No dot. Anthropic's tools[].name and Google's functionDeclarations[].name
impose equivalent restrictions, and every OpenAI-compatible endpoint inherits
the first one — in practice that means OpenRouter, Together, Groq, and most
self-hosted gateways.
These rules are not merely awkward together. They are mutually exclusive. The specification requires the character the tool APIs forbid. No conformant capability name could ever be passed to a tool-calling API.
This is not one vendor's quirk, and it is not something to wait out. It is the industry's settled convention, and we wrote a spec that could not meet it.
The part that is worse than the 400
An error at the boundary is the good case. You see it immediately and you fix it.
The real damage was quieter. Any client can cope — stripping a namespace is trivial. But the specification never said how, so every client invented its own mapping, and clients that agreed perfectly on the manifest stopped agreeing the moment they built tools from it.
Given com.example.orders.get:
| Strategy | Resulting tool name |
|---|---|
| Take the last segment | get |
| Replace dots with hyphens | com-example-orders-get |
| Take the last two segments | orders-get |
Three clients, one manifest, three different tool names. A model trained or prompted against one client's names does not transfer to another's.
Then it gets sharper. Consider a manifest advertising both:
com.example.orders.getcom.example.refunds.get
Under the last-segment strategy, both become get. The two capabilities collapse
into one identifier, silently. The model sees a single tool where two were
advertised, and which one actually executes depends on how the client happened to
build its dictionary. No error is raised. Nothing validates as broken. A refund
runs where an order lookup was intended.
There is also a length trap that has nothing to do with dots. namespacedName
permits 64-character segments with no limit on segment count, so a perfectly
conformant name can exceed the 64-character tool-name ceiling on its own, before
any transformation is applied.
What ADS-2 changed
The fix is deliberately small. ADS-2
adds one optional field, invocationName, and — more importantly — makes the
derivation rule normative so clients stop diverging.
{
"name": "com.example.shop.orders.get",
"invocationName": "orders-get",
"version": "1.0.0"
}
invocationName must match ^[A-Za-z][A-Za-z0-9_-]{0,63}$ and must be unique
across the manifest. Note that underscores are permitted here and prohibited in
name — the two fields have genuinely different jobs, and no single string could
have done both.
When it is absent, a client that needs a constrained identifier must derive
one by replacing every . with -, and must not derive one by truncating to
a subset of segments.
Truncation is prohibited rather than discouraged, and that distinction was the
main thing worth arguing about. Discouraging it would have left the orders.get
/ refunds.get collision legal. The failure it produces is silent, produces a
wrong action rather than an error, and is invisible in the manifest — so it has
to be a MUST NOT.
If the derived identifier still exceeds 64 characters, the client must treat the capability as not invocable through that interface and should say why, rather than truncating to fit. Failing loudly beats guessing.
One decision we are fairly confident about: invocationName carries no identity.
name remains the sole identifier. Two manifests describing the same capability
must agree on name; they need not agree on invocationName. Presentation
concerns should not become identity, or you get two competing identifiers and the
same divergence problem one layer up.
Every manifest valid under 0.2.0 remains valid under 0.3.0. The field is optional, and the derivation rule describes what careful clients were already doing.
What this looks like in practice
Our reference implementation serves 14 capabilities, each carrying both:
com.example.cheelashop.cart-add-item → cart-add-item
com.example.cheelashop.cart-clear → cart-clear
com.example.cheelashop.cart-remove-item → cart-remove-item
The left column is identity, stable and namespaced. The right column is what gets handed to a model. They are related by an explicit rule rather than by each client's guess.
The lesson we would like to have had earlier
A discovery format is not consumed in isolation. It is consumed by something, and that something has its own constraints — which are not negotiable just because your specification is elegant.
We validated our names against our own schema. We did not validate them against the API that the overwhelmingly common use of a discovered capability leads straight to. The schema was correct and the design was wrong, and no amount of internal validation would have caught it. It took building a client and getting a 400.
If you are writing a spec, the question is not only "is this internally consistent." It is "what will the first person to use this pipe it into, and what will that thing say."
ADS is MIT-licensed and open to proposals. If you think the derivation rule is wrong, or you have a case where prohibiting truncation causes more harm than it prevents, that is exactly the kind of disagreement the process exists for.
If you want to try the spec end to end, implementing agent discovery takes about ten minutes.