Hi — we run id.org.ai as an agent-auth surface and we've been implementing against auth.md. Some context on where we're standing: id.org.ai is our WorkOS AuthKit custom domain, so this is a customer conforming to your standard on a domain you issued us. Friendly footing, and nothing here is a complaint.
I've read the actual files rather than the landing page, so I can be specific about where we conform, where we don't yet, and three places where we carry something the spec doesn't have. No response needed if this isn't useful — but there's one narrow question at the bottom I'd genuinely like an answer to before we ship the wrong thing.
What I read
main at b53c9ed (2026-07-23):
AUTH.md — Steps 1–6, the error table, the revocation section.
agent-services/README.md — the agent_auth discovery block, the "Trust list" note, CIMD resolution.
CHANGELOG.md — v0.6.0 (service_auth/login_hint), v0.5.0 (auth_time + interaction_required/login_required), v0.4.0 (the claim-flow inversion).
agent-services/src/ — routes/well-known.ts, routes/agent-auth.ts, store.ts.
Where we conform today
Verified live against https://id.org.ai on 2026-07-27:
- Step 1a — RFC 9728 PRM at
/.well-known/oauth-protected-resource with resource / authorization_servers / scopes_supported / bearer_methods_supported, plus the §3.1 path-scoped variant.
- Step 1b — RFC 8414 AS metadata at
/.well-known/oauth-authorization-server carrying an agent_auth block with resolvable identity_endpoint, claim_endpoint, events_endpoint.
- ID-JAG verification —
typ: oauth-id-jag+jwt, urn:ietf:params:oauth:token-type:id-jag, audience-bound, alg:none refused, signature checked against a fixed trust anchor (never a caller-supplied jwks_uri).
- Revocation, registration layer — RFC 8417 SET over RFC 8935 push at
events_endpoint, application/secevent+jwt, 202 on a verified SET.
- Revocation, credential layer — RFC 7009
revocation_endpoint.
Where we don't conform yet, honestly: we have no /auth.md, no service_auth or anonymous registration types, no RFC 7523 jwt-bearer exchange, no auth_time freshness check, and our /agent/identity verifies an ID-JAG and returns a resolution rather than issuing a service-signed assertion. Those are our debts, tracked at dot-org-ai/id.org.ai#13 — I'm not asking you to change anything to accommodate them.
One that may be worth a glance since it bit us at the seam rather than in our own code: AUTH.md Step 3 says to mint the ID-JAG with aud = the resource from the PRM, and our PRM advertises a path-scoped resource (https://id.org.ai/mcp) while our identity endpoint checks aud against the origin. Ours to fix. But RFC 9728 permits several protected resources under one origin, and a service that runs both an MCP resource and an auth.md resource has to decide which resource the ID-JAG audience means. If Step 3 means "the PRM whose authorization_servers led you to this agent_auth block," saying that explicitly would have saved us the ambiguity.
Two things you may want regardless of the rest of this issue
1 · AUTH.md still tells agents to poll for a status field that no longer exists. The last line of the Errors section reads:
user_code errors are surfaced to the user on the claim page — they never reach you. Your poll keeps returning "status": "pending" until either the user gets the code right or the window expires ("status": "expired").
But Step 4c documents the poll returning {"error": "authorization_pending"} and {"error": "expired_token"}, and agent-services/src/routes/agent-auth.ts only ever emits status on the /agent/identity/claim initiation response ("status": "initiated"). Grepping the service for a poll-time status turns up nothing. This looks like prose left over from before #10 inverted the claim flow onto /oauth2/token with OAuth error vocabulary. An agent that implements that paragraph literally waits for a field it will never see.
2 · CHANGELOG.md stops at v0.6.0 (2026-06-10), but main carries behavior changes after it. #21 (merged 2026-07-22, b53c9ed) expanded revokeForDelegation to tear down the whole delegation — marking the id_jag registration revoked, dropping the claim handle so a surviving claim_token stops resolving, and making Registration.status return "expired" when revoked_at is set. That changes what an agent observes after a provider SET, and there's no version marker for it. With #22 still open on the same seam, an implementer pinning "auth.md v0.6.0" and an implementer pinning main are building against different protocols and have no way to tell.
Three things we carry above the spec
Filing these as proposals, not complaints — they're the reason I'd rather talk than fork.
A · The trust list has no machine-readable form, which makes Step 3 unfollowable. Step 3 tells the agent:
Before minting the ID-JAG, confirm your provider is on this service's trust list (publishing format is service-specific — check the AS metadata or service docs). If it isn't, fall back to service_auth or anonymous.
There's no field for it in agent_auth, and agent-services/src/routes/well-known.ts doesn't emit one. So in practice the only discovery path is to mint the ID-JAG, send it, and read issuer_not_enabled — which is exactly the trial-and-error the v0.6.0 changelog says agents shouldn't have to do:
agents cross-check identity_assertion.assertion_types_supported (provider trust setup isn't trial-discoverable)
The reasoning there argues for a discovery field and then the field isn't there. We publish ours: a POST /agent/host/register registry that binds host_id → iss + jwks_uri (SSRF-gated, CAS-protected against a first-registration race, 409 on hijack), and the bound jwks_uri is the only key we'll verify an ID-JAG against. Something as small as agent_auth.identity_assertion.issuers_supported: ["https://provider.example.com"] — or a URL to a list, if enumerating trusted providers is itself sensitive — would let an agent make the Step 2 decision without burning an attempt. I appreciate the "Trust list" note's point that display names must be service-controlled and never pulled from the ID-JAG; a discovery field of issuer URLs only wouldn't undercut that.
B · The agent is never a principal, so revocation can't reach one agent. Throughout the flow sub is the human and the agent is an unnamed actor behind an assertion. The SET handler makes this concrete — agent-auth.ts calls revokeForDelegation(verified.claims.iss, verified.claims.sub, verified.claims.aud), so the unit of revocation is the (iss, sub, aud) triple. If a user runs two agents from the same provider against the same service and wants to kill one, I don't see the expressible operation; the provider's only lever tears down the delegation both agents ride on.
We model the agent as a first-class principal with its own identifier and a delegation pattern, and our /agent/identity already carries agent_id and an RFC 8693-style act claim through from the ID-JAG. If those survived into the registration record and the access_token, per-agent revocation and per-agent audit would both fall out of what's already on the wire. Entirely possible this is deliberate — one delegation per (provider, user, service) is a defensible simplification — but if so it'd be worth AUTH.md's revocation section saying so, since "the user's only kill switch" (#21's phrasing) is coarser than an operator would expect.
C · A scope has no ceiling. api.write authorizes writing, unboundedly. An agent registered for api.write on a payments API is authorized for a $10 transfer and a $10,000,000 one identically, and the only place to put the limit is out of band. We carry a ceiling alongside the grant (an amount cap, a per-action cap) as part of the authorization itself. I notice better-auth's AAP put a constraints: {amount: {max: N}} object on its grants, so there's at least one other implementation that felt the same pressure. I'm not proposing you adopt a constraint grammar — but if a scope could optionally carry service-defined bounds that the consent screen in Step 3 renders, the user's one consent gate would be showing them a much more meaningful thing than a scope string.
The narrow question
Is the trust list deliberately out-of-band, or would you take a discovery field for it?
That's the one that changes what we build next. If it's deliberate — you don't want services enumerating their trusted providers to unauthenticated agents — say so and I'll document attempt-and-issuer_not_enabled as the intended path and stop. If it's just not written yet, I'd be glad to open a PR adding an optional issuers_supported to the agent_auth.identity_assertion block plus the AUTH.md Step 2/3 prose, shaped however you'd want it.
One adjacent question I'll ask only if the answer to the first is "yes, we'd take it," so as not to load this up: whether identity_types_supported is meant to be extensible enough to advertise a different claim channel. Our human-ownership ceremony proves control of a repository via GitHub Actions OIDC rather than a 6-digit code in a browser — same role in the flow, no human at a keyboard. Right now we can't advertise that in an auth.md-legible way, so we'd have to run both ceremonies. That's fine if the user_code ceremony is meant to be the only one; I just don't want to guess.
Status, plainly: we're an implementer, not a standards body, and we're not asking you to endorse anything. Thanks for auth.md — the two-hop discovery and the decision to keep the claim grant off the IANA device_code URN are both details we'd have gotten wrong on our own.
Hi — we run id.org.ai as an agent-auth surface and we've been implementing against auth.md. Some context on where we're standing: id.org.ai is our WorkOS AuthKit custom domain, so this is a customer conforming to your standard on a domain you issued us. Friendly footing, and nothing here is a complaint.
I've read the actual files rather than the landing page, so I can be specific about where we conform, where we don't yet, and three places where we carry something the spec doesn't have. No response needed if this isn't useful — but there's one narrow question at the bottom I'd genuinely like an answer to before we ship the wrong thing.
What I read
mainatb53c9ed(2026-07-23):AUTH.md— Steps 1–6, the error table, the revocation section.agent-services/README.md— theagent_authdiscovery block, the "Trust list" note, CIMD resolution.CHANGELOG.md— v0.6.0 (service_auth/login_hint), v0.5.0 (auth_time+interaction_required/login_required), v0.4.0 (the claim-flow inversion).agent-services/src/—routes/well-known.ts,routes/agent-auth.ts,store.ts.Where we conform today
Verified live against
https://id.org.aion 2026-07-27:/.well-known/oauth-protected-resourcewithresource/authorization_servers/scopes_supported/bearer_methods_supported, plus the §3.1 path-scoped variant./.well-known/oauth-authorization-servercarrying anagent_authblock with resolvableidentity_endpoint,claim_endpoint,events_endpoint.typ: oauth-id-jag+jwt,urn:ietf:params:oauth:token-type:id-jag, audience-bound,alg:nonerefused, signature checked against a fixed trust anchor (never a caller-suppliedjwks_uri).events_endpoint,application/secevent+jwt, 202 on a verified SET.revocation_endpoint.Where we don't conform yet, honestly: we have no
/auth.md, noservice_authoranonymousregistration types, no RFC 7523 jwt-bearer exchange, noauth_timefreshness check, and our/agent/identityverifies an ID-JAG and returns a resolution rather than issuing a service-signed assertion. Those are our debts, tracked at dot-org-ai/id.org.ai#13 — I'm not asking you to change anything to accommodate them.One that may be worth a glance since it bit us at the seam rather than in our own code: AUTH.md Step 3 says to mint the ID-JAG with
aud= theresourcefrom the PRM, and our PRM advertises a path-scopedresource(https://id.org.ai/mcp) while our identity endpoint checksaudagainst the origin. Ours to fix. But RFC 9728 permits several protected resources under one origin, and a service that runs both an MCP resource and an auth.md resource has to decide whichresourcethe ID-JAG audience means. If Step 3 means "the PRM whoseauthorization_serversled you to thisagent_authblock," saying that explicitly would have saved us the ambiguity.Two things you may want regardless of the rest of this issue
1 ·
AUTH.mdstill tells agents to poll for astatusfield that no longer exists. The last line of the Errors section reads:But Step 4c documents the poll returning
{"error": "authorization_pending"}and{"error": "expired_token"}, andagent-services/src/routes/agent-auth.tsonly ever emitsstatuson the/agent/identity/claiminitiation response ("status": "initiated"). Grepping the service for a poll-timestatusturns up nothing. This looks like prose left over from before #10 inverted the claim flow onto/oauth2/tokenwith OAuth error vocabulary. An agent that implements that paragraph literally waits for a field it will never see.2 ·
CHANGELOG.mdstops at v0.6.0 (2026-06-10), butmaincarries behavior changes after it. #21 (merged 2026-07-22,b53c9ed) expandedrevokeForDelegationto tear down the whole delegation — marking the id_jag registration revoked, dropping the claim handle so a survivingclaim_tokenstops resolving, and makingRegistration.statusreturn"expired"whenrevoked_atis set. That changes what an agent observes after a provider SET, and there's no version marker for it. With #22 still open on the same seam, an implementer pinning "auth.md v0.6.0" and an implementer pinningmainare building against different protocols and have no way to tell.Three things we carry above the spec
Filing these as proposals, not complaints — they're the reason I'd rather talk than fork.
A · The trust list has no machine-readable form, which makes Step 3 unfollowable. Step 3 tells the agent:
There's no field for it in
agent_auth, andagent-services/src/routes/well-known.tsdoesn't emit one. So in practice the only discovery path is to mint the ID-JAG, send it, and readissuer_not_enabled— which is exactly the trial-and-error the v0.6.0 changelog says agents shouldn't have to do:The reasoning there argues for a discovery field and then the field isn't there. We publish ours: a
POST /agent/host/registerregistry that bindshost_id→iss+jwks_uri(SSRF-gated, CAS-protected against a first-registration race, 409 on hijack), and the boundjwks_uriis the only key we'll verify an ID-JAG against. Something as small asagent_auth.identity_assertion.issuers_supported: ["https://provider.example.com"]— or a URL to a list, if enumerating trusted providers is itself sensitive — would let an agent make the Step 2 decision without burning an attempt. I appreciate the "Trust list" note's point that display names must be service-controlled and never pulled from the ID-JAG; a discovery field of issuer URLs only wouldn't undercut that.B · The agent is never a principal, so revocation can't reach one agent. Throughout the flow
subis the human and the agent is an unnamed actor behind an assertion. The SET handler makes this concrete —agent-auth.tscallsrevokeForDelegation(verified.claims.iss, verified.claims.sub, verified.claims.aud), so the unit of revocation is the (iss, sub, aud) triple. If a user runs two agents from the same provider against the same service and wants to kill one, I don't see the expressible operation; the provider's only lever tears down the delegation both agents ride on.We model the agent as a first-class principal with its own identifier and a delegation pattern, and our
/agent/identityalready carriesagent_idand an RFC 8693-styleactclaim through from the ID-JAG. If those survived into the registration record and the access_token, per-agent revocation and per-agent audit would both fall out of what's already on the wire. Entirely possible this is deliberate — one delegation per (provider, user, service) is a defensible simplification — but if so it'd be worth AUTH.md's revocation section saying so, since "the user's only kill switch" (#21's phrasing) is coarser than an operator would expect.C · A scope has no ceiling.
api.writeauthorizes writing, unboundedly. An agent registered forapi.writeon a payments API is authorized for a $10 transfer and a $10,000,000 one identically, and the only place to put the limit is out of band. We carry a ceiling alongside the grant (an amount cap, a per-action cap) as part of the authorization itself. I notice better-auth's AAP put aconstraints: {amount: {max: N}}object on its grants, so there's at least one other implementation that felt the same pressure. I'm not proposing you adopt a constraint grammar — but if a scope could optionally carry service-defined bounds that the consent screen in Step 3 renders, the user's one consent gate would be showing them a much more meaningful thing than a scope string.The narrow question
Is the trust list deliberately out-of-band, or would you take a discovery field for it?
That's the one that changes what we build next. If it's deliberate — you don't want services enumerating their trusted providers to unauthenticated agents — say so and I'll document attempt-and-
issuer_not_enabledas the intended path and stop. If it's just not written yet, I'd be glad to open a PR adding an optionalissuers_supportedto theagent_auth.identity_assertionblock plus the AUTH.md Step 2/3 prose, shaped however you'd want it.One adjacent question I'll ask only if the answer to the first is "yes, we'd take it," so as not to load this up: whether
identity_types_supportedis meant to be extensible enough to advertise a different claim channel. Our human-ownership ceremony proves control of a repository via GitHub Actions OIDC rather than a 6-digit code in a browser — same role in the flow, no human at a keyboard. Right now we can't advertise that in an auth.md-legible way, so we'd have to run both ceremonies. That's fine if theuser_codeceremony is meant to be the only one; I just don't want to guess.Status, plainly: we're an implementer, not a standards body, and we're not asking you to endorse anything. Thanks for auth.md — the two-hop discovery and the decision to keep the claim grant off the IANA
device_codeURN are both details we'd have gotten wrong on our own.