A publisher migrates to AdCP and publishes /.well-known/adagents.json. One row in authorized_agents names their Prebid Sales Agent with delegation_type: "direct" and exclusive: true. The buyer agent treats that as the only path for the property.
The same week, nobody edits ads.txt. It still lists two other exchanges as RESELLER for the same seller account. A supply-path optimizer fetches ads.txt, sees an authorized resale hop, and green-lights a bid whose source.schain includes one of those exchanges. Both files return HTTP 200. Neither file's validator reads the other.
That is not a hypothetical layering bug in one vendor stack. It is two trust artifacts on the same origin answering different questions with no cross-check.
What exclusive actually scopes
In the AdCP adagents schema, exclusive on an authorized_agents entry means this agent is the publisher's sole authorized path for the inventory slice covered by that entry (property ids, placement tags, countries, and the rest of the scope fields). When exclusive is false or absent, other authorized agents in the same file may sell the same slice.
That boolean lives entirely inside adagents.json. It does not emit an HTTP DELETE to ads.txt. It does not flip a RESELLER row to DIRECT. It does not remove a seller id from the IAB flat file buyers have been crawling for a decade.
AdCP's own comparison table maps programmatic ads.txt to adagents.json with delegation_type on the publisher side. The spec explains that adagents.json is more expressive: properties, placements, delegation type, time bounds, and exclusivity inside one JSON document. None of that documentation promises that publishing adagents.json supersedes or reconciles ads.txt on the wire.
ads.txt still encodes relationship as a single token per line: DIRECT or RESELLER. A RESELLER line is an explicit statement that the listed advertising system may resell inventory for that publisher id. An exclusive direct agent in adagents.json is a parallel statement in a richer grammar. A buyer that only ingests AdCP can believe resale is closed. A buyer that still runs ads.txt plus schain validation can believe resale is open. Both can be "correct" relative to the file they read.
Where the hop still gets judged
Programmatic paths still arrive as OpenRTB. source.schain names the advertising systems that touched the impression. Supply-path products compare those nodes to ads.txt, sellers.json, and internal allowlists. They do not today fetch adagents.json on every bid and ask whether exclusive: true forbids the node they are staring at.
The inverse gap is also live: adagents.json can authorize an agent URL that never appears as asi on the schain attached to the impression you actually bought. Valid authorization in the well-known file and a valid seller node in schain are independent passes.
For the shape of schain itself, missing hp on a node under SupplyChain 1.1 expectations is a structural failure mode worth separating from authorization confusion. The openrtb.schain.node.hp_missing rule reference documents when a reader expects hp and the node omits it. Duplicate adjacent nodes are a different class of bug; see openrtb.schain.duplicate_node. Fixing schain syntax does not tell you whether the publisher meant to allow that seller in adagents.json or ads.txt.
How you catch it
My manual check for this mismatch is dull and effective: for the same registrable domain, fetch adagents.json and ads.txt, resolve the seller ids and agent URLs on the exclusive row, and list every ads.txt line that still says RESELLER for overlapping seller accounts. If any RESELLER row names a system that can still inject schain nodes on live traffic, exclusive in adagents.json did not close the path your SPO tool uses.
Once the buy collapses back to OpenRTB, paste the bid request into the OpenRTB tester on RTBlint's site or run rtblint validate on the JSON. That pass catches schain structure, version drift, and field placement. It does not fetch publisher adagents.json and diff it against ads.txt; nobody should pretend a schema pass on the request replaces the two-file comparison.
Agent stacks that emit AdCP or ARTF output face the same split. An MCP tool can return OK from get_adcp_capabilities while the downstream RTB request still carries a schain node ads.txt authorizes. RTBlint's OpenRTB validator MCP and get_adcp_capabilities discovery belong on the agent side; they check the payload the model or service emitted, not the publisher's twin trust files on the origin. If you are wiring an AdCP buyer, you still need an explicit policy step that reconciles adagents exclusivity with ads.txt before you trust exclusivity in a deal brief.
Implementation teams piping agent output through CI should treat rtblint (cargo install rtblint, npm install rtblint-core, or the MCP server) as the deterministic check on the OpenRTB object after the agent claims success. The wire contract for schain and imp objects does not move because the RPC returned OK. These packages are independent of IAB Tech Lab and of AgenticAdvertising.org; they do not replace reading both publisher files when the question is whether resale is still allowed.
Where to go next
The IAB agentic advertising standards guide situates adagents.json beside AAMP, ARTF, and AdCP without collapsing them into one file. For how schain nodes relate to bidstream identity fields once a request is in flight, what bidstream data is walks the object model buyers actually log.
If you already covered adagents.json versus schain, that post is about hop-level disclosure versus publisher authorization. This failure is earlier on the origin: exclusive in JSON versus RESELLER in plain text. A path can fail either test, both, or neither, while still clearing HTTP and JSON Schema on every fetch.
Until something compares those two files on every property migration, exclusive: true is a promise inside adagents.json, not a revocation in ads.txt. Buyers who still read both get two answers. Buyers who read only one get a false sense of closure.
Top comments (0)