Agent Authorization
A DNS-based standard that lets a domain explicitly opt in to being contacted by autonomous agents — over email or any other channel — and scope which agents or platforms it accepts, per channel.
Status: Draft standard, version
AGENTS1. Vendor-neutral and provider-agnostic: the record, the tags, and the matching rules name no provider and assume no single channel.primitive.devappears only as an example provider identifier andexample.comappears as an example domain.The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, MAY, RECOMMENDED, and NOT RECOMMENDED are used per RFC 2119/RFC 8174.
Quick start
If you just want to let agents on a given platform email your domain, publish
one DNS TXT record at _agents on your domain (using primitive.dev as the
example provider):
_agents.example.com. IN TXT "v=AGENTS1; p=accept; channel=email; allow=provider:primitive.dev"
No record means no permission, and you can change or remove it at any time. The rest of this document defines the grammar, the trust model, and the exact evaluation rules.
Abstract
Authentication standards answer "is this message really from the domain it claims?" — SPF, DKIM, and DMARC for email, and analogues elsewhere. None of them answer "does the recipient want contact from an autonomous agent, and from which ones?"
As autonomous agents become common — and as they reach domains over more
than one channel — recipient domains need a single, public, machine-readable
signal: "I accept agent contact, here is who I accept it from, and over
which channels." Agent Authorization is that signal: one DNS TXT record
the recipient publishes at _agents on its own domain.
It is the mirror image of authorization-to-send standards like SPF. SPF authorizes servers to send as a domain. This record authorizes agents to make contact with a domain. Control of the domain's DNS authenticates the publisher's opt-in intent — only the domain owner can publish the record. It does not, by itself, authenticate the agent or provider making contact; that is the asserting provider's responsibility today, with hooks reserved (see Security considerations) to layer in verifiable proof without a breaking change.
The standard is split into two layers:
- A channel-agnostic core — the record, its grammar, the provider/principal trust model, the version-selection and evaluation algorithm. None of it is email-specific.
- One or more channel bindings — each defines a channel token (e.g.
email) and the two channel-specific details the core leaves open: what the channel principal is, and how principal values are compared. Email is the first binding.
Terminology
The standard deliberately separates identities that channel authentication usually conflates:
- Agent — an autonomous program that initiates contact (sends mail,
opens a session, posts a message) without a human composing each exchange.
For evaluation an agent is described by an open set of attributes its
provider asserts and has verified.
AGENTS1defines two attributes,providerand the channelprincipal; future versions MAY define more (e.g. a per-agentid). Consumers MUST treat the attribute set as open, not as a fixed tuple, so new attributes are additive. - Provider (agent platform) — a service that hosts agents, identified
by a provider identifier: a domain name it controls, e.g.
primitive.dev. The identifier is channel-independent: trusting a provider means trusting it to vouch for the agents it hosts, on any channel. - Channel — a transport over which an agent reaches a domain (email today; others may be registered later). Each channel has a registered channel token and a binding (see Channels).
- Channel principal — the per-channel identity an agent presents and the
channel has verified. What the principal is, and how it is compared, are
defined by the channel binding — it is a domain for some channels, but MAY
be a public key, a handle, or another identifier for others. The core never
assumes its shape. For the email binding the principal is the DKIM-aligned
From domain, and the
domain:token (below) is its principal-grant form. - Publisher (recipient domain) — the domain that publishes the
_agentsrecord and thereby opts in. The "you" in this document.
The provider/principal split is the core of the trust model. A publisher can
trust a whole platform ("any agent Primitive hosts is fine") or a
specific principal ("only agents presenting acme.com, whoever hosts
them"), and these are different decisions — on any channel.
The record
A publisher opts in by publishing one or more TXT records at the _agents
label of the domain that will be contacted:
_agents.example.com. IN TXT "v=AGENTS1; p=accept; channel=email; allow=provider:primitive.dev"
Lookup procedure
A consumer evaluating contact to <domain> over channel C:
- Queries
TXTat the exact name_agents.<domain>.AGENTS1performs no ancestor tree-walk: absence at the exact name means not opted in for<domain>. Subtree inheritance is reserved for a futuresp=tag, whose absence MUST be treated assp=strict(no inheritance). Naming this default now means already-published apex records keep a stable meaning whensp=ships. - Consumers MUST support EDNS0 and TCP fallback. A truncated or oversized answer MUST be retried over TCP, never silently treated as "no record."
- NODATA (the name exists but has no
TXT) is treated identically to NXDOMAIN: not opted in. An empty or whitespace-onlyTXTstring is a malformed record and is ignored (net: not opted in). ACNAMEat_agents.<domain>is followed normally by the resolver, and the resultingTXTset is evaluated as if published at_agents.<domain>.
Version selection
The v tag carries the format version (AGENTS1). Versioning and the
multiple-record rule are defined together so they never fight:
- A consumer MUST ignore any record whose
vit does not recognize. - Version selection runs first. Among records it recognizes that name
channel
C, the consumer determines the highestvit understands and discards all records namingCwith a lowerv. Records that differ only invare NOT a collision — this is how a publisher runsAGENTS1and a futureAGENTS2side by side at one name during a transition. The same-version duplicate check (below) then applies only to the surviving highest-version records; a duplicate at a superseded version never affectsC. v=AGENTS1is reserved for breaking grammar changes only. Backward- compatible evolution uses new tags (ignored by older consumers) and the critical-tag mechanism (below), not a version bump.
Multiple records and channels
A domain MAY publish multiple v=AGENTS1 records at _agents, each
governing a different channel (or set of channels). Evaluation selects by
channel:
- Records that do not name channel
Care ignored forC, so an unrelated channel's record never blocksC. - If exactly one
AGENTS1record namesC, it governsC. - If more than one same-version record names
C, the configuration is ambiguous and fails closed forC(not authorized). Fail-closed is chosen deliberately: under DNS forgery, denial is the safe failure, whereas a permissive merge would let a forged duplicate broaden access. Because this is also a griefing/tamper vector (someone who can inject one extra record can deny a channel), consumers SHOULD log/alert on duplicate- channel records rather than denying silently, MUST NOT cache the duplicate state as a sticky negative, and publishers SHOULD deploy DNSSEC. - A record naming multiple channels (
channel=email chat) asserts that itsallowset is identical for every channel listed. A publisher needing differentallow/policy per channel MUST use separate records. - When a record names several channel tokens, a consumer evaluates it for any
listed token it understands and ignores listed tokens it does not. An
unrecognized channel token alongside a recognized one (
channel=email foo,foounknown) does not invalidate the record for the recognized channel.
DNS encoding constraints
- A record value longer than 255 octets MUST be split into multiple
<character-string>s within a single RR. Consumers MUST concatenate all character-strings of an RR with no separator before parsing (the DKIM / RFC 1035 rule). This is the single most common TXT implementation error; consumers that read only the first string will misparse long records. - Keep the full
_agentsresponse small enough to fit a single UDP/EDNS0 answer where practical. Publishers with largeallowenumerations SHOULD use the reserved.well-known/agentscompanion (see Future extensions) rather than growing the TXT set unboundedly. - This standard uses the
TXTRRTYPE exclusively. A dedicated RRTYPE is out of scope and SHOULD NOT be introduced; the deprecated SPF RRTYPE (RFC 7208 §3.1) is the cautionary precedent.
Syntax
The record value is a list of key=value tags separated by semicolons,
following the same lexical conventions as SPF and DMARC:
record = "v=AGENTS1" *( OWS ";" OWS tag )
tag = [ "!" ] key "=" value
key = ALPHA *( ALPHA / DIGIT / "_" )
value = *( %x09 / %x20-3A / %x3C-7E ) ; any printable ASCII + SP/HTAB, except ";" (%x3B)
OWS = *( SP / HTAB ) ; ALPHA/DIGIT/SP/HTAB are [RFC 5234] core rules
- Tokenization is two levels. A record is split into tags at each
;. A tag'svalueruns from the=to the next;or end of record; leading and trailingOWSin a value is stripped. There is no escape mechanism inAGENTS1, so a value MUST NOT contain;. For list-valued tags (channel,allow), the stripped value is then split into tokens on runs of ASCII space, HTAB, and/or comma; interior whitespace in a list value is a separator, not data. - Tag keys are case-insensitive, and the
!critical marker is case-irrelevant (!REQ≡!req). Value case rules are per-tag: channel tokens and provider identifiers are ASCII case-insensitive; principal values are compared per the channel binding (which may be case-sensitive, e.g. a public key). "Case-insensitive" in this document means ASCII case- folding only. - The
v=AGENTS1tag MUST come first. A record whose first tag (afterOWStrimming) is notv=AGENTS1is malformed and MUST be ignored — consumers do not reorder to findv. - A tag whose key appears more than once makes the record malformed; the record MUST be ignored (fail-closed; avoids first-wins/last-wins divergence).
- Unknown ordinary tags MUST be ignored (forward compatibility).
- Critical tags — a tag key prefixed with
!is critical. A consumer that does not understand a critical tag MUST treat the record as not authorized rather than ignoring the tag. This reserves the ability to ship a tightening change (e.g.!req=) that older consumers cannot silently bypass — the escape hatch a blanket "ignore unknowns" rule otherwise forecloses.
Tags
| Tag | Required | Meaning |
|---|---|---|
v |
yes | Format version. MUST be AGENTS1. |
p |
yes | Policy: accept opts in, reject is an explicit opt-out. |
channel |
yes | One or more registered channel tokens (whitespace- or comma-separated) this record governs, e.g. email. |
allow |
when p=accept |
Whitespace- or comma-separated authorization tokens (below). An empty allow= under p=accept authorizes nothing (deny-all) and is not malformed. Absent allow under p=accept makes the record malformed (ignored); use explicit allow= for deny-all. allow under p=reject is ignored, not malformed. |
req (critical-capable; written !req) |
no (reserved) | Critical strictness requirements (e.g. dnssec, signed, verified-provider). Reserved; defined by a future version. Because it is written critical (!req), an older consumer that sees it fails closed rather than honoring a weaker policy. |
policy |
no | Absolute https:// URL to a human-oriented acceptance policy. Advisory and never authoritative for the accept/reject decision. A malformed value MUST be ignored without invalidating the record. |
contact |
no | Abuse / operations contact as a mailto: URI. Advisory; malformed value ignored without invalidating the record. |
A record whose channel tag is missing, empty (channel=), or names no
registered channel the consumer understands is, for that consumer, simply not
applicable to any channel it can evaluate; it MUST be ignored (not malformed,
not treated as governing some default channel).
Any p value other than accept is treated as reject — for both the
authorization decision and caching — so an unknown/typo'd policy fails safe.
allow tokens
Each token in allow is a typed grant. The type prefix distinguishes a
provider grant from a principal grant:
| Token | Grants |
|---|---|
* |
Any compliant agent on any provider. NOT RECOMMENDED — the broadest opt-in and a standing spam target; prefer enumerated grants. Tooling SHOULD warn when a publisher selects it. |
provider:<provider-id> |
Any agent hosted by the named provider. Trust the platform. |
domain:<principal> |
The email binding's principal grant: any agent whose verified channel principal (DKIM-aligned From domain) is the named domain, regardless of provider. Other bindings define their own principal-grant token(s). |
agent:<id> |
Reserved for a future per-agent identity axis. Will be namespaced (agent:<id>@<provider>) and cryptographically attested. Unrecognized in AGENTS1 and MUST be ignored. |
Rules:
provider:and principal grants (domain:) are orthogonal; a publisher may list any mix.*is matched literally and is NOT canonicalized.*carrying an ignored|-qualifier (*|rate=10) still reduces to the base token*and authorizes all inAGENTS1(qualifiers are advisory here).- A token with no recognized type prefix (a bare
primitive.dev) MUST be ignored, so future token types degrade safely. - The vertical bar
|is reserved as a future per-token qualifier delimiter (e.g.domain:acme.com|rate=10).AGENTS1consumers MUST ignore everything from the first|in a token onward and match only the base token. Reserving it now keeps future per-grant scoping additive instead of colliding with the;tag separator. - Canonicalization before comparison (provider ids and
domain:-style domain principals): strip a single trailing., trim surrounding whitespace, ASCII-lowercase, then convert to IDNA2008 A-label (punycode) form. Reject the token — drop that grant only, do not invalidate the record — if it is empty or fails IDNA2008 A-label conversion (matching the bare-token degradation above). The same canonicalization is applied to the incomingagent.providerand to a domain-shapedagent.principalbefore comparison, so both sides are A-labels and comparison is octet-equality. Non-domain principals are canonicalized and compared per their binding.
Channels
A channel is a transport with a registered channel token and a binding that supplies the two details the core leaves open: what the channel principal is, and how principal values are compared. Everything else — record, grammar, trust tokens, version selection, evaluation, caching — is shared across all channels.
Channel tokens and allow token-type prefixes live in shared registries (see
IANA considerations). Tokens containing a . or prefixed x- are reserved
for private use and MUST NOT be registered, so experimentation cannot
squat the global namespace.
Email (channel token: email)
The first defined binding.
- Channel principal = the agent's DKIM-aligned From domain (the
RFC 5321 / RFC 5322 author domain whose DKIM signature passes and
aligns). A
domain:acme.comgrant authorizes agents that send DKIM-aligned mail fromacme.com. - Principal comparison = domain canonicalization above (A-label, ASCII case-insensitive).
- The record is consulted on the outbound path, keyed on the recipient's
domain (the part after
@). The principal SHOULD be one the channel authenticates end-to-end (the receiving side can re-verify DKIM alignment), not merely the asserting provider's self-report.
Future channels register a token and define their own principal and comparison. The core does not change.
Evaluation (authorizing contact)
A contacting agent is described by provider-asserted, channel-verified
attributes; AGENTS1 reads two:
agent.provider— the provider identifier the agent runs on.agent.principal— the agent's verified channel principal, per the binding for the channel in use.
To decide whether contact to <domain> over channel C is authorized:
- Resolve
TXTat_agents.<domain>per Lookup procedure. - If resolution is inconclusive (SERVFAIL, timeout, truncation that TCP retry did not resolve): the result is unknown. Treat as not authorized for this decision, but MUST NOT cache it as a negative (see Caching); retry later.
- Discard records whose
vis unrecognized or malformed (per Syntax). From the remainder, take the records namingC, and select the single governing record per Version selection and Multiple records and channels (zero → not authorized; same-version duplicates → not authorized). - If any critical tag (
!-prefixed) in the selected record is not understood → not authorized. Ifpis notaccept(including unknown/typo'd values) → not authorized. Ifp=acceptbutallowis absent → the record is malformed → not authorized (an explicit emptyallow=is the deny-all form, and authorizes nothing). - Contact is authorized iff any
allowtoken matches (after canonicalization):- token is
*, OR - token is
provider:XandX == agent.provider, OR - token is a principal grant (
domain:Yfor email) andY == agent.principalunder the binding's comparison.
- token is
Fail-closed is the rule throughout: absence, ambiguity, malformed records, unknown critical tags, and lookup failure all resolve to not authorized. Opting in is an explicit, unambiguous act.
Pre-flight / dry-run mode
A consumer MAY evaluate without a bound agent identity (a debugger asking "what
would happen?"). With agent.provider/agent.principal unknown, * and
known-value checks that don't depend on the missing attribute may still
resolve, but a provider:/domain: decision that depends on an unknown
attribute is indeterminate and MUST be reported as such (treated as not
authorized), never silently skipped or guessed. If any token resolves to a
definite match (e.g. *), report authorized; report indeterminate only
when no token definitely matches and at least one depends on a missing
attribute.
Example
_agents.example.com. TXT "v=AGENTS1; p=accept; channel=email; allow=provider:primitive.dev domain:acme.com"
_agents.example.com. TXT "v=AGENTS1; p=accept; channel=chat; allow=*"
(chat is hypothetical, used only to illustrate multi-channel publishing;
email is the one channel AGENTS1 defines.) Evaluating an email contact
(only the first record names email):
- Agent on
primitive.dev, principalbot.thing.io→ authorized (matchesprovider:primitive.dev). - Agent on another platform, principal
acme.com→ authorized (matchesdomain:acme.com). - Agent on another platform, principal
random.net→ not authorized.
Caching
The record is read on the hot contact path, so implementations SHOULD cache,
keyed on the publisher domain (cache the full _agents record set once and
select by channel in memory — not per channel):
- Honor the record's DNS TTL, with a sane floor and a ceiling. For
p=acceptthe positive-cache ceiling SHOULD be short (≤1h) so revocation takes effect quickly;reject/absence fail safe and MAY be cached longer. - Negative results (no record /
reject) MAY be cached but MUST honor the zone SOA negative-TTL as an upper bound (RFC 2308) and SHOULD default short. If the resolver does not surface the SOA negative-TTL (common with high-level/DoH resolvers), fall back to the short default and never cache a negative longer than that default. - Inconclusive lookups (SERVFAIL, timeout) MUST NOT be cached as negative; retry, so a transient resolver blip never becomes a sticky "this domain rejected you."
- On a credible abuse signal for a publisher, a consumer SHOULD re-resolve that publisher's record immediately, bypassing cache.
Implementation note: DNS-over-HTTPS hides SERVFAIL behind an HTTP-200 envelope
with Status: 2 and an empty answer set. Consumers MUST read the response
Status, not the answer count, to honor the inconclusive rule above.
Security considerations
- DNS control authenticates intent, not the contacting party. Publishing
the record proves the publisher opted in. It does not prove the agent
or provider is who it claims; in
AGENTS1those are the asserting provider's word (see below). Treat the record as the publisher's consent, layered with provider-side and channel-native authentication. - Forgery can over-permit without DNSSEC. An on-path or cache-poisoning
attacker on an unsigned zone can fabricate a more permissive record
(
allow=*) and manufacture consent the publisher never gave. So the worst case is not merely denial — without DNSSEC it includes over-permission. DNSSEC is RECOMMENDED; consumers SHOULD prefer authenticated (AD-bit) results and MAY require them (future!req=dnssec) for*grants or any grant used to bypass an otherwise-applicable sender restriction. - The asserter is the beneficiary. In the email binding the consumer
running evaluation is often the sending provider itself, asserting "this is
DKIM-aligned." The decision SHOULD be anchored to a receiver-verifiable
result (the receiving side re-checks alignment), not a self-report. A future
signed-assertion mechanism (reserved via
!req=signedand a proof axis) lets a relying party verify the(provider, principal)binding without trusting the provider's word. - Provider-identifier impersonation. Nothing in
AGENTS1bindsprovider:Xto the entity controllingX; a bad actor can self-assert any provider id. A future version will require proof of control of the provider domain (a provider-published record at a reserved label and/or a discoverable signing key). Until then, publishers SHOULD understandprovider:trust is only as strong as the ecosystem's provider verification. - Class-based, not per-message. A match authorizes a category on a
channel, not a specific message or session, and does not replace per-contact
abuse controls (rate limiting, content policy, suppression). The reserved
per-agent axis (
agent:) and per-token qualifier delimiter (|) are the path to finer scoping. allow=*is the open relay of agent auth and makes a domain a soft target; it is NOT RECOMMENDED and MAY be deprecated in a future version.- The record is a public recon oracle.
_agents.<domain>advertises, to adversaries too, who accepts agent contact and how loosely. Publishers SHOULD avoid*, use a role/abuse address forcontactthat can absorb load, and prefer narrow grants. - Revocation is cache-bounded. A compromise-then-opt-in attacker's runway
is the positive-cache TTL; the short
p=acceptceiling and abuse-triggered re-resolve above exist to bound it.
Reserved for future hardening (no breaking change required)
Unknown ordinary tags are ignored and unknown critical tags fail closed, so the
following can be added to AGENTS1 records without a version bump:
!req=strictness (dnssec/signed/verified-provider) — critical, so older consumers can't silently honor a weaker policy.- A proof/signature axis for verifiable
(provider, principal)assertions. - A provider proof-of-control contract at a reserved provider-side label.
- Namespaced, attested per-agent identity (
agent:<id>@<provider>). - A machine-readable abuse/revocation feedback hook.
Conformance
A compliant consumer implements the lookup procedure, version selection,
multiple-record and critical-tag rules, canonicalization, and fail-closed
evaluation above. A compliant provider asserts only (provider, principal)
pairs it has actually verified for the channel in use, honors the publisher's
contact, and does not assert a provider id it does not control. "Compliant"
in allow=* and elsewhere refers to this profile; it is a behavioral
commitment, not a certification, and provides no security on its own — see
Security considerations.
Future extensions (non-normative)
Reserved for later versions; an AGENTS1 consumer ignores ordinary ones and
fails closed on critical ones it does not understand:
!req=strictness; signed assertions / proof axis; provider proof-of-control.agent:<id>@<provider>per-agent identity;|per-token qualifiers (rate=,for=).sp=subdomain policy (defaultstricttoday) and an org-boundary tree-walk.- Additional channel bindings beyond
email. - A
.well-known/agentsHTTPS companion for large or signed policies. If introduced it is advisory only unless a future version explicitly makes it authoritative via a critical pointer tag; the TXT record remains the authoritative accept/reject source forAGENTS1.
IANA considerations (registries)
To keep a vendor-neutral namespace from fragmenting, this standard anticipates three registries (registration policy: Specification Required / Expert Review):
- Channel tokens (e.g.
email). allowtoken-type prefixes (e.g.provider,domain,agent).- Tag keys (e.g.
v,p,channel,allow,req,policy,contact), noting which are critical-capable.
Names containing a . or prefixed x- are reserved for private use in all
three and MUST NOT be registered.
Provider implementation note (non-normative)
A contacting provider integrates the standard by, on each outbound contact,
resolving _agents.<recipient-domain>, selecting the record for the channel in
use, and evaluating it against the agent's asserted-and-verified attributes per
the algorithm above. Practical guidance learned from the email binding:
- Resolve off the hot commit/transaction path (as SPF is checked outside the message-commit critical section); pass the resolved record set into the evaluation as data so the matcher does no network I/O.
- Carry the lookup as a three-state result —
found/empty/inconclusive— not a nullable list, and map DoHStatus/ resolver error codes into it so the "don't cache inconclusive as negative" rule is implementable. - An inconclusive lookup is a retryable condition, not a denial; it MUST NOT be recorded as a permanent rejection of the recipient.
Because the check sits alongside a provider's existing contact authorization, it is one additional path to "allowed", not a replacement for sender-side requirements (a verified identity, authenticated sending, etc.).