AdCP / Advertising Context Protocol.
A practical guide to the open protocol layer for agentic advertising workflows: discovery, planning, buying, creative, signals, governance, accounts, and measurement.
AdCP gives advertising agents a shared task and schema layer. It helps buyers, sellers, platforms, data providers, and creative systems communicate through standardized advertising actions rather than bespoke dashboard workflows and one-off integrations.
AdCP is not a replacement for OpenRTB, clean rooms, or human judgment. It is a protocol layer for making advertising work discoverable, executable, governable, and auditable by agents.
- AdCP 3.1 GA · v3.1.23 · Sep 2026
- AdCP 3.2 rc.3 · dev & staging only · Sep 14, 2026
- AdCP 3.0 prior line · wire-compatible
- v2 deprecated Aug 1, 2026
- Sponsored Intelligence experimental
Part of AdCP on No Fluff Advisory — the spec explainer
Fast read
- What it is
- An open agentic advertising protocol that defines domain-specific tasks and schemas for advertising agents.
- What it does
- Helps agents discover inventory, set up accounts, buy media, manage creative, activate signals, apply governance, and report results through standardized calls.
- What it is not
- Not a media exchange, identity graph, DSP, SSP, clean room, or replacement for OpenRTB.
- Why it matters
- Agentic workflows need repeatable, machine-readable actions. Without a shared protocol, every agent has to learn every platform separately.
- Current state
- AdCP 3.1 remains the general-availability line and the production recommendation; the latest stable tag is v3.1.23 (GitHub, September 14, 2026), whose #7406 backports the <code>required_tools</code> OR-gate fixes to 3.1.x: <code>canonical_format_validate_input</code> drops <code>comply_test_controller</code> (filed as #7404; #7405 on main), and four storyboards drop <code>get_adcp_capabilities</code> (the applicable subset of #6774). #7451 corrects the v3.1.22 release record to name the request-aware cache partitioning that shipped without changelog coverage. The 3.2 line is at v3.2.0-rc.3 (September 14, 2026), which the official versions page marks development and staging only — pin "3.2-rc.3" solely against peers that advertise it, and keep production on wire pin "3.1". The versions page still says no SDK embeds rc.3; that lapsed on September 15, 2026, when @adcp/sdk 14.0.0-rc.37 and Python adcp 8.0.0b15 both shipped declaring ADCP_VERSION 3.2.0-rc.3. Only Go adcp/v3.2.1 remains on rc.1. The docs are explicit that patch components are never sent. 3.1 is additive over 3.0 and supported patches do not change stable wire surfaces, so a 3.0 integration keeps working with no code change; v3.1.22 did change the explicitly experimental TMP contract, under the emergency exception to the normal notice process. v3.1.3 was withdrawn days after shipping (see the version-watch section below). Validate implementation details against current official docs.
- Best next step
- Understand the domains, then decide which workflow your company should expose or consume first.
What AdCP is.
AdCP is a shared advertising task layer for AI agents. It defines what agents can ask for, what systems should return, and how actions move through the advertising workflow.
| Layer | Job | Example |
|---|---|---|
| Transport | Carries messages | MCP / A2A |
| AdCP | Defines advertising tasks and schemas | get_products, create_media_buy, get_signals, activate_signal |
| Execution system | Performs the action | publisher, SSP, DSP, creative system, data provider |
| Governance | Controls authority and audit | approvals, signatures, idempotency, audit logs |
| Existing ad standards | Power underlying objects and transactions | OpenRTB, AdCOM, OpenDirect, Deals API |
Why AdCP exists.
Fragmented workflows
Media buying, creative, data activation, accounts, and reporting still live across disconnected systems and dashboards.
Agents need actions, not screens
An agent cannot scale by clicking through every platform UI. It needs task-level interfaces.
Common schemas reduce ambiguity
Natural-language briefs need to resolve into structured, auditable requests.
Human oversight needs architecture
Agentic buying requires approvals, limits, audit logs, and accountability built into the flow.
Platform participation needs standards
Publishers, data providers, creative systems, and ad platforms need a shared way to expose what agents are allowed to do.
What’s new in 3.1 — and what 3.2 changes.
AdCP 3.1 is now the current general-availability line (latest stable tag v3.1.23, with the 3.2 line now at rc.3 for development and staging only), additive over 3.0. The 3.2 line has been in release candidate since September 3, 2026 and moved three times in eleven days, most recently to v3.2.0-rc.3 on September 14 — which the official versions page restricts to development and staging. Separate what shipped from the obligations adoption brings from what 3.2 is about to change.
Confirmed — AdCP 3.1 (GA · v3.1.23)
- AdCP 3.1 is the current general-availability line — GA at GitHub tag v3.1.0 (June 18, 2026), patched through v3.1.2 (June 30 – July 8, 2026; protocol-only fixes and spec clarifications). v3.1.3 (July 13) was withdrawn days later — official notice: it added a stable protocol schema field in a patch release, violating the 3.1.x versioning contract, plus a publisher-domain filter bug accepting a plural selector shape the schema rejects; the docs still route implementers around it to v3.1.2. Patches then ran from v3.1.4 (July 19) to v3.1.23 (September 14, 2026, the latest stable tag). v3.1.21 (September 9) was a single change, #7384 (the 3.1.x backport of #7383): guaranteed idempotency replay scoped to state-mutating requests, an optional idempotency_key on the get_products request schema, read wrappers tolerating the key without claiming read replay, and the read-tool-idempotency storyboard relaxed to match — withdrawing the 3.1-era plan to make reads reject a missing key at 3.2. The v3.1.22 changelog lists two: legacy format asset declarations aligned with the canonical asset union, guarding individual and repeatable-group variants against drift (#7423), and a Trusted Match privacy correction restricting Context Match embeddings derived from non-public single-user content while requiring privacy reduction for free-form context signals (#7424 — the 3.1.x backport of #7396, which this site’s author filed as #7393). Treat that changelog as accurate but not complete: the tag also carries #7425 (commit 251df51), which backports the request-aware cache partitioning from #7397 to 3.1.x and carried no changeset, so the changelog has no line for it. #7451 merged on September 13, 2026 to correct the v3.1.22 record, and it landed the next day in v3.1.23 as commit 8a933df. Every 3.1.x release body on GitHub except the withdrawn v3.1.3’s is version-only — literally the string "AdCP 3.1.20" — so per-patch detail lives in the repo changelog, not the release notes. The 3.2 line is now at v3.2.0-rc.3 (September 14, 2026), the current signed protocol checkpoint, restricted to development and staging. The SDKs caught up within a day: @adcp/sdk 14.0.0-rc.37 (September 15, 02:39Z) and its successor rc.38 declare ADCP_VERSION 3.2.0-rc.3 and bundle the rc.3 compliance cache and schemas, Python adcp 8.0.0b15 (02:33Z the same morning) ships ADCP_VERSION 3.2.0-rc.3, and only Go adcp/v3.2.1 still embeds rc.1. The versions page on main still names TypeScript RC.35, Python beta.14 and Go 3.2.1 as all embedding rc.1 and says no SDK embeds rc.3 — held there on purpose rather than merely stale: with Go on rc.1 there is no pin common to all three SDKs, so the maintainers are waiting to align the doc once against rc.3 instead of twice, and the Go catch-up is tracked in adcp-go#536. The rendered docs site has published no rc.3 snapshot at all. The wire-negotiation pin is the string "3.1"; SDKs normalize "3.1.x" → "3.1", and the docs forbid sending patch components. v2 is fully deprecated on August 1, 2026 (UTC).
- Additive over 3.0 at the wire level: no required field was removed and no shape broke, so a 3.0-conformant integration keeps working with no code change.
- A production-hardening release: release-precision version negotiation (supported_versions[]), distributed and cryptographically signed brand trust (self-published brand.json + signed verify_brand_claim), canonical creative formats plus a transformers catalog, and vendor-attested measurement goals.
- Operational guarantees: idempotency_key required on every state-mutating request (v3.1.21 walked back the every-request rule — reads keep the key optional and wrappers must tolerate it); dependency-impact / media-buy health signals, buyer-side webhook observability, and reporting finality + reach-window semantics.
- Feature detail is sourced from the official "What’s New in 3.1" and 3.0→3.1 migration docs plus the SDK notes — the GitHub release records only the version and date.
Obligations when you claim 3.1
- Advertise supported_versions in get_adcp_capabilities and echo the served version at the envelope root; transports must tolerate unknown envelope-root fields.
- Brand agents implementing verify_brand_claim(s) must return a signed (JWS) response.
- Move creative metadata to canonical format_kind + list_transformers, and pass the 3.1 compliance storyboard bundle to earn the 3.1 badge.
- Schema tightening can surface as errors: trademarks[].status is now a typed enum and trademarks[].countries must be ISO 3166-1 alpha-2 (former free-text values fail); cache_scope is schema-required on get_products / get_signals.
- Authentication hardens: RFC 9421 request signing — recommended in 3.0 — becomes REQUIRED for mutating and financial operations in 3.1+, with static bearer/Basic credentials prohibited there (still permitted for read/discovery). Until you land on 3.1, bearer-over-TLS remains the effective floor for spend operations — the spec says to ship 9421 ahead of the cutover.
- The Go SDK (adcp-go) has breaking type changes — plan the upgrade; it is not a drop-in.
In release candidate — AdCP 3.2 (v3.2.0-rc.3)
- The 3.2 line has been in release candidate since September 3, 2026 and moved three times in eleven days — rc.1 on September 5, rc.2 on September 13, rc.3 on September 14. The docs call rc.3 the current signed protocol checkpoint and restrict it to development and staging — pin "3.2-rc.3" only against peers that advertise it and keep production on wire pin "3.1". The SDKs caught up on September 15: @adcp/sdk 14.0.0-rc.37 and rc.38 declare ADCP_VERSION 3.2.0-rc.3, Python adcp 8.0.0b15 embeds it too, and only Go adcp/v3.2.1 still embeds rc.1. The versions page has not, and says so on purpose: with Go still on rc.1 there is no pin common to all three SDKs, so it aligns once when Go lands (adcp-go#536).
- Reliable Reporting becomes a discoverable capability rather than an assumption: a seller advertises it, so a buyer can tell before spending whether the reporting contract is one it can hold the seller to.
- Vendor measurement gets a closed vendor_relationship enum — first_party / affiliated / third_party — so a seller has to declare when it is its own measurement vendor. Absence means undeclared, not independent.
- Delivery and reach semantics tighten: cross-buy delivery aggregates are deprecated in favour of media-buy-level currency, and deduplicated cross-buy reach is disambiguated from summed reach so the two stop being reported as the same number.
- The scheduled removal lands here — legacy top-level media-buy status moves to media_buy_status — and typed validation and precedence for pixel-ratio registry metadata begins in 3.2, where 3.1 SDKs only preserve it.
Watch next (4.0 and beyond)
- A 4.0 deprecation set: Format.pricing_options, legacy SignalId / flat signal targeting, inline brands[], data_provider_signals.
- Flagged experimental in 3.1, not yet stable: the measurement-vendor catalog (measurement.core) and the responsive_creative / sponsored_placement / agent_placement format kinds.
- A proposed pre-GA rename of body-level status discriminators (e.g. check_governance) was discussed (#4897) but is unconfirmed in the final tag — validate before relying on it.
- AdCP Verified independent auditing, signals-protocol maturity, registry adoption, SDKs, and reference implementations.
Migrating 3.0 → 3.1 — the checklist
A 3.0 integration keeps working untouched — 3.1 is additive at the wire. “Migrating” means claiming 3.1, and each step below is a real obligation (per the 3.0→3.1 migration guide), not optional polish. Backward compatibility is “mostly”: 3.0 clients are not broken, but adoption plus the Go SDK upgrade impose real changes — plan it.
- Advertise versions — Return supported_versions[] from get_adcp_capabilities and echo the served version at the envelope root; make transports tolerate unknown envelope-root fields (this replaces prior reject-on-unknown validation).
- Sign brand verification — If you implement verify_brand_claim(s), return a signed (JWS) response bound to the task, resolved tenant, agent URL, and freshness window.
- Adopt canonical creative — Move creative metadata to the canonical format_kind set and discover build units through the new list_transformers catalog; format-attached pricing moves to the transformer.
- Fix typed fields — trademarks[].status must be a typed enum and trademarks[].countries ISO 3166-1 alpha-2 — former free-text values now error; add the now-required cache_scope on get_products and get_signals.
- Require idempotency — Send idempotency_key on every state-mutating request (sellers may reject missing keys there; reads keep it optional as of v3.1.21); treat IDEMPOTENCY_IN_FLIGHT as transient rather than minting a fresh key.
- Upgrade the SDK, then certify — The Go SDK (adcp-go) has breaking type changes — re-decode against the new shapes; it is not a drop-in. Then pass the 3.1 compliance storyboard bundle to earn the 3.1 badge.
Contributions to the protocol.
This page is written from inside the work: No Fluff Advisory builds and operates a live AdCP signals agent (listed in the AAO registry) with all-pass conformance runs across the AdCP 3.0 and 3.1 GA suites (57/57 scenarios on the current 3.1.20 line), and co-leads the Signals & Measurement working group. For the live state of the registry itself — every registered agent, its actual tool catalog, and real request/response captures — see the AdCP Ecosystem Tracker. For the buy side of the same protocol run end to end — discovery, audience targeting, brief, media buy, creatives, the human gate, delivery reporting — with every call copy-pasteable against a live seller agent, see the AdCP Buyer’s Guide.
Merged into the protocol & tooling
- last_updated on signal-definition — schema field for verifiable signal-record freshness (#5248 → PR #5249, in the 3.1.0 line since rc.7).
- Root-cause diagnostics for the conformance runner — a schema-invalid
get_adcp_capabilitiesresponse used to surface as N unrelated track failures with nothing linking them; reported it (#6254) and wrote the protocol-side contract, which now defines thecapabilities_response_schema_invalidnotice and an RFC 6901capability_pointerfield so one cause is reported once, ahead of the results it explains (PR #6256, merged August 8, 2026). - Runtime signal-quality attestations — our RFC for verifier-signed signal-quality evidence on check_governance (#5418) landed in the protocol's 3.2 line:
runtime_attestations[]consuming the shared attestation primitive (#4529), scoped to signal activation with GDPR/CCPA deactivation exempt, with response-side evaluations and binding digests (PR #6218, merged August 6, 2026). - Compliance-grader fix — all-pass single-protocol runs rendered “Degraded” by a rollup regression; root-caused to the introducing commit, filed (#5429, triaged P0) and fixed (PR #5444, merged).
- RFC 9421 webhook-signing default — the inventory (#4270) and draft PRs (#4273/#4275, closed in favor of the triage-generated PR the maintainer merged) that drove the 3.0.9 realignment away from the deprecated HMAC framing. The migration tracker (#4205) closed completed on August 13, 2026: reframed as an on-ramp problem rather than an adoption threshold, with the docs and signing guide leading with RFC 9421 and no separate DRI left to assign.
- A dense vector on the rung that means “don’t share the content” —
context_signals.embeddingis the one field on Trusted Match's classified-output tier that can be partially inverted back to a user's own words, and on anai_assistantproperty the “content environment” is one person's conversation turn. Filed against v3.1.21 with the inversion literature and two additive text edits (#7393); shipped in #7396, which took the stricter route — a flat prohibition rather than the demonstrate-on-request carve-out I proposed — and added policy-filtering rules onkeywordsandsummarythat the issue had not asked for. - Context Match over-caching — split out of the embedding issue at the triage's request, since it carried no privacy dependency: the recommended cache key was placement-only while responses vary with artifact, classified signals and geo, so a router could serve one session's decision against another's context (#7414). The collision was fixed in #7397, which replaced the key with
{provider_id, context_hash}, a SHA-256 over the forwarded request minus$schemaandrequest_id. That PR was not a response to #7414. It closes #7395, which was filed under the maintainer’s account from the same #7393 review. Both were opened on September 10, 2026, more than 18 hours before I filed #7414. A bot-triage note marked #7414 blocked on #7397 four minutes after I filed it, and the issue was closed as not planned on September 13 with no comment. Thecache_ttl: 0line did ship, by another route: the #7395 bot triage listed it, my September 10 review of #7397 flagged it as missing, and it was added on September 11. It went in as a hit-rate note (geo or per-session signals produce many distinct hashes), not the correctness SHOULD #7414 proposed, and the 3.1.x backport (#7425) carried the same sentence into v3.1.22. The docs release-notes page goes further. Its Version 3.1.22 section, added by #7451 and first tagged in v3.1.23 (September 14, 2026), says Context Match router operators “affected by unsafe response caching MUST bypass or disable that caching until the router implements both request-aware partitioning and a trusted namespace for every result-affecting security and provider-configuration context.” Main #7446 would add that namespace ascache_namespace, 3.2-only, with October 25, 2026 as the earliest date its enforcement can publish, and is still open as of September 16, 2026. - Radio & static-OOH measurement parity — scoped the two channels the delivery contract had no shape for (#6138, with Chris Williams), then filed the asks that came out of it: radio delivery-currency, meaning row-level
measurement_sourceplusnielsen_audionotation (#6139), and spot-level as-run for linear (#5682). Every ratified decision is merged —measurement_sourceon delivery rows (PR #6241),nielsen_audiowith notation-vs-provider language and the radio reconciliation guide (PR #6836, field-name correction PR #6856), reporting cadence extended to measured channels and then promoted to a normative MUST (PR #6240, PR #6860), and spot-level as-run reporting (PR #6574). Taxonomy graduation criteria split out to #6261 and closed when media channels went stable (PR #6542). Implementations were maintainer-authored.
Conformance suite, hardened
- Signals-only agents now skip non-applicable media-buy gating and gained dedicated error-handling / schema-validation storyboards (#2916, #3350, #4009 — fixed in the 3.1.0 suite; earlier #2535, #3999, #4065).
- An assertion no signals-only agent can satisfy —
webhook_emission's syntheticassert_contributionstep grades a branch set whose only contributors requireget_products; a signals-only agent's contributors correctly skip asmissing_tool, and the assertion then fails on the empty set as a[core]failure nothing on the agent side can clear. The storyboard's owngrading.pass_criteriasays those steps grade not-applicable, and a sibling storyboard already guards its assertion phase withskip_if(adcp-client#2862, filed with the runner's machine-readable failure record —expected: [synchronous_completion_handled], actual: []— as the reproduction). Fixed September 14, 2026 in #7504, which gated all three phases of the synchronous webhook-emission branch family onmedia_buy.buying_modescarryingwholesaleand added a regression asserting that a signals-only agent grades the whole branch familynot_applicable— the grading contract the filing argued from. Note where it landed: the runner'sassert_contributionbehaviour is preserved and the storyboard was gated instead, so the fix rides the spec repo rather than the SDK. - Reviewed with wire data — the signal-definition epistemic / provenance layer (#5017, opened under the maintainer account): backed a progressive-disclosure concern that another reviewer had already raised, with three things: wire sizes from our signals agent (88,590 bytes for 5 signals against the 64 KB probe ceiling, pretty-printed — an estimated 60 KB compact — and carrying our own
x_*extension blocks, most of each signal’s bytes by our posted breakdown, but none of #5017’s new fields), an estimate that those fields would add 3–5 KB per signal, and three ways to split discovery from lookup. Before the merge, the maintainer account added compact-by-default guidance and afieldsselector onget_signals. Our field-level schema notes followed about an hour after the merge; the follow-up (#5190, merged May 30, 2026) addressed them, not always as proposed. Neither the pre-merge commits nor #5190 cites our comments, though the maintainer account introduced the selector commit as a follow-up “based on the progressive-disclosure discussion”, which our comment was part of. - A shared reference primitive that stayed a draft — the signal epistemic-model RFC, opened under the maintainer account (#4616), listed three decisions for the working group to make once. The first: should one reference primitive be reused across markets, ID graphs, taxonomies, measurement currencies and panel frames, or should each get its own per-dimension type? I filed a draft PR for the shared option, described in its own text as a discussion artifact rather than a merge bid. It proposed one
{system, value, version?, name?}reference for values defined in external identity, taxonomy, geographic and measurement systems, plus a per-deployment fidelity enum and a conversion descriptor (#4622, opened May 16, 2026). One reviewer approved its second revision round the same day, after I folded in their suggestions; five more rounds followed. Review comments under the maintainer account on May 25, 2026 questioned the premise: calling these “systems” is confusing, “Not clear to me how this generalized system helps us”, and the conversion is not per-signal but happens at activation, across graphs held by several parties, with no one knowing how many times. One comment’s stated main point was to separate “what is this signal” from how it is connected to the delivery surface. The same day I narrowed the descriptions in a seventh round and offered to drop the conversion descriptor, or to close the PR if the rows it targeted turned out to need one dominant system each. The next comment under the maintainer account was the closing one, on May 30, 2026. It called the PR a discussion artifact rather than a merge candidate, said the system-reference idea “remains relevant” for concrete row-level work and should come back as a fresh, narrow PR once one of those rows is ready to adopt it, and said it “was not closed by #5017”, which had handled signal-definition enrichment without adopting these primitives. It was never merged, and none of its three schemas is onmainas of September 16, 2026. Separately, on May 28 and 29, I had written on #5017 that “the per-dimension shape vs. shared-primitive question is resolved” and that its per-dimension direction was “the right one”. #4616 was closed on August 16, 2026 as completed, with a comment calling it superseded framing and saying the actionable signal epistemic decisions are now owned by concrete signal RFCs and implementation issues. Its text has marked the third decision “decided for 3.2” since August 4, 2026, and has never marked the reference-shape question decided. - Wholesale cache-scope isolation — found that conditional-fetch tokens pass the suite even when a global token leaks across
cache_scopelayers; filed (#5739, bot-triaged ready-to-implement and milestoned 3.2.0) and implemented the fix — scope-isolation storyboards for signals and products, the schema MUST-NOTs, and the reference-agent correction (PR #5748, merged August 3, 2026). - Grader reliability — evaluate_agent_quality returning identical scores across four runs including a force_refresh pass; filed with live wire evidence (#6206, bot-triaged same day: the silently-dropped
force_refreshparameter confirmed as a real grader bug; the score-staleness root cause resolved agent-side, follow-up scoping in #6233). - Storyboard-runner account resolution — advertising
list_accountssilently switches the runner into discovery mode, which then hard-fails as “ambiguous” on any multi-tenant roster instead of falling back to the natural-key path that exists a few lines above (#6242, triaged P0 and fixed — schema-default account routing and agency-operated brand matching shipped in@adcp/sdk13.0.0-rc.11 via adcp-client#2488, with runner guidance in #6289; two implementers hit it independently), and the amplification pattern behind it — one schema-invalid capabilities response surfacing as N unrelated track failures (#6254; I first filed the link between the two as a separate issue by mistake, #6255, and closed it 98 seconds later on August 6, 2026 in favour of a comment on #6242). - 3.1.11 harness regression — the dynamic creative-asset directive shipped without its expansion step ordered ahead of the runner's own outbound validation, so conformant sellers were graded down on requests their agents never received (#6267, fixed; the affected track returned to a full pass on the first post-deploy regrade). Filed alongside the contract gap behind it — unbuildable asset slots were charged to the agent, and the storyboard contract contradicted itself on whether to fail the step or skip it — which resolved in the seller's favour: runner-owned coverage gaps now grade not-applicable rather than counting as failures, with the fixture set widened and slot-content guessing prohibited (#6269, fixed). Registry card rendering a stale tool count fixed alongside (#6268).
- Compliance reporting that hides its own verdict — the badge is gated on storyboard-level results, but the API returned the summary count with an empty breakdown (#6291) and a track could display every scenario passing while the verdict that actually gates the badge disagreed (#6292) — both fixed and shipped together. Found while diagnosing why a live agent sat at 132/138 scenarios and no badge. Once the breakdown shipped it showed the card reporting a declared specialism as failing while the underlying record read untested with zero steps attempted and zero failures (#6302) — a storyboard that had never run once being displayed as one that ran and failed.
- A badge panel that names the wrong blocker — the AAO Verified panel tells a seller to “fix the failing storyboards in this card” over a record reading 13 passing, 4 partial, 18 untested, and zero failing: every non-passing state renders as “failing” (#6363, bot-triaged as a classification bug, then fixed by a maintainer PR — the panel now names the actual blocking state).
- Registry-migration QA — five filings in one morning — probing the agenticadvertising.org migration surfaced: verified media-buy agents rendering “Not Verified” badges (#6376 — our hyphen/underscore hypothesis was superseded by the bot triage citing migration 502, which does bulk-revoke every 3.1 badge pending GA — the real cause, a migration that bulk-revoked every 3.1 badge pending GA); an unstructured badge-role error and an OpenAPI description still advertising roles that don’t exist,
salesincluded (#6377, corrected mid-flight — the 400 does carry a body; our first probe read only the status code); the 200-plus-grey embed contract for unverified agents (#6378, resolved documented-intentional, with theonerrorembed pattern to be deprecated in favour of the gate-on-verification pattern this site already ships); the hosted grader still running a superseded spec line hours after release — the operational sibling of #6321 (#6379); and the CLI producing silent mixed-line runs, kit from 3.1.13 while storyboards resolve from bundled 3.1.11 with no consistency check (#6380, reproduced as a two-run diff that cost a full grading pass). A sixth followed the same afternoon when a requeue ran the 3.0.18 cache against this 3.1-only agent and flipped the public card to failing before any scenario executed: sellers migrating 3.0 → 3.1 have no self-serve way to update the compliance target, with the proposed fix of resolving it from advertisedsupported_versionson every health check (#6383). All six closed within a day: the badge trio via PR #6387, the grader deployment after a version-manifest root-cause (#6379), the mixed-line CLI defect with a fail-closed line-inference fix (adcp-client#2518), and the compliance-target trap with owner re-detection plus registry safeguards that refuse verdicts against untrusted fallback targets (#6422/#6426, closing #6383). - One runner deployment, six defects — attributed by controlled experiment — a grader failure that would not reproduce locally was run to ground with a differential wire test: the same
get_productscall against the same database in the same minute returns the correct USD-pruned result withfilters.pricing_currenciespresent and reproduces the grader's failure byte for byte without it, so the deployed runner is dropping the filter (#6439, carrying the runner-half question #6379's closure left open). The same stripping family surfacedbrandsent onlist_accountswhile the published request schema defines no such field — the registry noticing agents for omitting a field the spec never told them exists (#6437) — and the SDK validating capabilities against a 3.1.0-beta.7 enum that rejects scenario names its own 3.1.13 storyboards gate on (adcp-client#2524). A droppedsandboxflag rounded out the family: the same account identity hashed to a second, configless account, root-caused by recomputing the tenancy-triple hash for field-subset variants and closed agent-side by seeding the triple from the resolved principal. Four runner defects, one deliberate decline, zero unattributed failures on the card. - SDK test-fixture bug — the @adcp/sdk scenario suite's sync_creatives fixture fails the SDK's own request validation (adcp-client#2443, bot-triaged with a corrected fix and regression-test ask, both shipped in adcp-client#2463/#2473; blast radius scoped to the local scenario suite in #6233).
- Conformance steps no seller can pass — three findings from taking one live agent from 147 to 240 passing steps. The storyboards mark validations
severity: advisoryand the spec says an advisory failure does not fail the step, but the runner implements none of that contract, so every validation on the stale-cache step is advisory and the step is unpassable by construction (#6320, triaged P0); a second instance of the same skip-graded-as-failure pattern turns up in the idempotency rate-limit step, which is doubly unreachable because its test kit is sandbox-scoped while the graded URL is production (#6344). And a seller who declaressupported_billing: ['operator']is asked for an error code that means the opposite of what is true —BILLING_NOT_PERMITTED_FOR_AGENTimplies the mode exists and this agent may not use it, where the honest answer is that the mode is not offered at all (#6343) — the same shape as the specialism gap below. - Grades sellers cannot reproduce — the hosted grader ran compliance cache
3.1.12while no published SDK carried it:latestshipped 3.1.5, the release candidate 3.1.11, and a step that passed locally failed on the grader with no way to see the storyboard that failed it (#6321, triaged P0; the merged fix gated the version resolver on published bundles). Found alongside a registry state machine that locks honest sellers out of grading entirely: a completed run rewrites the requested compliance target to whatever it just ran, so an agent that narrows its advertisedsupported_versionsto the one version it actually supports then fails pre-flight against the older cache and is graded on nothing at all. Narrowing was the honest move and it took the public card from degraded to failing. - A storyboard that fails the path the spec prescribes — a seller with no task layer is told by the spec to allocate the media buy id at commit and return synchronous success with
confirmed_at: null, then the async storyboard grades that seller on a submitted-task envelope it was never meant to emit (#6345). Triage first assumed omitting the controller scenario made the storyboard grade not-applicable; a live reproduction showed the force step grades not-applicable while the dependent step runs anyway and fails. Fixed: the storyboard now gates on the forcing scenario's advertisement and skips before any phase executes (PR #6368, merged), with the skip-misclassification the review surfaced corrected downstream in the SDK (adcp-client#2511). - One filing withdrawn — an argument that the audit-observation contract forced sellers to relay a verifier's verdict they never obtained (#6322). The premise was wrong: the schema already separates the two, carrying what a submission claimed in one field and what a verifier observed in others, and the only permitted code says
CLAIMED. Retracted with the correct reading (#6323) and the feature built the honest way instead — recording the claim, attributing the observation to this agent, and leaving every verdict field empty. - 53 of 62 failing steps on two public cards were ungradable — the hosted grader admits a storyboard when any one of its
required_toolsis present, while the suite selector then required all of them, socanonical_format_validate_inputwas selected for agents with novalidate_inputand failed all 17 of its steps on each card. Separately, steps the SDK marksnot_applicablewere graded as failures on the hosted runner but skipped locally. Filed with both runner source lines and a local reproduction (#7404, bot-triaged P0); this was the second time the OR gate was fixed. The first was #6773 in August —billing_gate_dispatchlistingget_adcp_capabilitiesbesidesync_accounts, which every conformant agent satisfies, so signals-only agents were admitted and failed the first step instead of skipping. #6774 merged inside fourteen hours and review widened the one-line deletion to 17 storyboards. It landed on main only: the 3.1.x cherry-pick its own PR body called warranted was never made, so the storyboards kept mis-selecting on the 3.1.20 line the hosted grader actually runs. The September fix is that backport finally arriving — the storyboard half merged on September 14, 2026, #7405 on main and the 3.1.x backport #7406 twenty-one seconds later, this one written from here rather than upstream — and shipped the same afternoon in v3.1.23, whose changelog names the filing: agents withoutvalidate_inputnow take a coverage-gap skip. The second half has since moved. #7454, merged September 13, 2026 and linked in the thread that day, gradesfixture_seed_unsupportedand similar skip reasons as not applicable. On the signals card the two storyboards filed under this half,deterministic_testingandidempotency, have readuntestedat 0/0 since September 14. They are no longer failures, though not the passes the local SDK reports either. Those runs also reportsdk_version14.0.0-rc.35, while #7454 was built against rc.33, so the shift cannot be credited to #7454 alone. A follow-up on September 15 missed that and argued the public feed could not represent a skipped step at all. The feed already could, and a correction on September 16 says so. The issue was retitled at 10:15 UTC on September 16, 2026, three minutes after that correction, to “Hosted grader: 11 failing steps remain on two cards after #7454/#7405/#7406 (sales 10, signals 1); budget-truncated runs still published as complete grades”. The count matches both cards’ latest runs; how much of the drop those three PRs caused is less clear. Both cards still grade on the 3.1.20 bundle, published September 2, before #7405 and #7406 merged, and the one signals failure, inbilling_gate_dispatch, is still there, as it was when this thread counted it on September 13. None of the ten sales failures is in a storyboard the filing named, and the 17canonical_format_validate_inputfailures on the sales card cleared because this agent now implementsvalidate_input. On truncation, the filing’s third report: the sales card’s two short runs on September 9 and 10, at 130 and 124 scenarios against 193 in a full run, still readcompleteand authoritative in the public history feed. PR #7453, merged September 13, 2026, stops runs the SDK markstimed_outfrom replacing the public grade; PR #7506, which would do the same for runs that report no completeness at all, is still an unmerged draft. - Grades that cannot be attributed — two heartbeats of the same agent build, with no deploy between them, disagreed by a scenario. Nothing in the public compliance feed identified the grader that ran them, so a runner that changed under you and a runner that is simply non-deterministic looked identical from outside. Asked for one field on each history entry (#7429). It arrived by another route. #7454, merged September 13, 2026, writes a provenance block on each new history entry, including the exact SDK version, the compliance bundle and a grading-policy version. Runs graded before it was deployed carry none. #7429 is still open, and #7433, a separate PR to persist the runner version, is still an unmerged draft.
- A control that reports success and leaves no trace — an owner requeue on September 7 produced no run and no history row, and the feed’s newest
owner_testentry predated the click by 24 days. Filed as a two-way fork, silent no-op or ran-and-discarded-as-unchanged (#7428, bot-triaged bug and P1 eight minutes after filing). Closed as verified on September 15, 2026 with a third answer. Requeue comply only schedules the next heartbeat, by design; the click fell inside a 19.4-day window, August 19 to September 8, in which the registry graded the agent not at all, and on September 16, 2026 the feed still showed those 465.6 hours with no rows; and a fresh click with devtools open put three runs through admission to completion on September 13, which the maintainer confirmed end-to-end. The original click left no retained HTTP evidence, so its cause is unrecoverable, and the confirmation fixes triage scoped, alast_rechecked_aton the card and a flash that does not auto-dismiss, did not ship with the closure. One thing this filing should not claim: the requeue semantics were not a discovery. They were written down in #4265’s body in May 2026 — the fix our own #4253 was closed against — where the endpoint is described as clearinglast_checked_atso the agent is eligible on the next tick. We filed four months later against behaviour already documented in a thread we opened. - One gate, two meanings —
required_toolsis read by two code paths that disagree: the suite selector excludes a storyboard when any listed tool is missing, the per-storyboard runner admits it when any listed tool is present. A storyboard naming two tools against an agent holding one is therefore skipped by the first and admitted-then-failed by the second. Filed after the same defect recurred on a line an earlier data fix had not reached — adcp-client#2652 was closed on the strength of a storyboard-side change, andbilling_gate_dispatchmis-selected again four weeks later on 3.1.20 (adcp-client#2882, bot-triaged bug / conformance / ready-for-human). The bot triage confirmed the divergence against published 13.0.2 and the convergence already written incomply.ts, and put the choice of semantic to the maintainer, who shipped any-of rather than all-of, which is only safe because the storyboard-side fixes strip the universal capability tool fromrequired_toolsfirst. It shipped in 13.0.4 on September 14, 2026 (comply.js:600now readsrequired.some(…)) and the issue closed the same day. Against the sales agent the change is measurable: 305 storyboards selected on 13.0.2 became 348 on 13.0.4 with byte-identical caches, so the delta is the selector alone, and four of the newly admitted storyboards fail. - A grader that disagrees with itself between two of its own commands — since
@adcp/sdk13.0.3,storyboard rungrades everyresponse_schemacheck on acomply_test_controllerstep as failed withno_schema_registered, whilestoryboard steppasses the identical step seconds later on the same SDK with the schema id resolved. The agent's response is conformant either way — the controller arms and echoes the forced task id (adcp-client#2867). It was the reason both of our agents stayed pinned to 13.0.2 rather than trackinglatest— the newer build could not grade its own controller surface. Root-caused by the bot triage toisExternalResponseSchemaAuthoritativetreating a test-harness tool like a protocol tool, fixed in adcp-client#2894 on the 13.x line, released in 13.0.4 on September 14 and closed the same day. Both agents now track 13.0.4. - A heartbeat that had not run in four days, and the question that stayed open — the dashboard advertised a recheck cadence it was not keeping: no scheduled run against the signals agent in four days while a manual evaluation of the same agent passed 30 of 30 (#4253). #4265 shipped three causes the next day —
last_checked_atwas stamped beforecomply()ran, so a crash inside the window silenced the agent for up to 12 hours a cycle; every heartbeat row was writtendry_run: trueand so never reached the dashboard; and manual runs wrote only to a table the dashboard does not read. Two asks did not ship: the last-automated versus last-manual distinction, and an answer to why the tick itself had not fired. The closing comment carved that second one out as a known follow-up — a scheduler concurrency limit of five against an unbounded wait queue, where five hung jobs block the heartbeat indefinitely — deferred until production diagnostics existed. By September 15, 2026 a stall of that shape showed fleet-wide: across all 35 registered agents, heartbeat runs went from 25–36 a day over September 8–13 to six on the 14th and one on the 15th, with no heartbeat anywhere in the registry for fourteen hours and 30 of the 31 agents with a heartbeat on record past a day since their last. Filed with the fleet numbers as #7546, pointing at the follow-up rather than asserting it. The filed sweep missed one of the 35 feeds, so the issue shows five and 29 of 30. Bot-triaged seven minutes later as a bug,priority:P0, P0 Bugs milestone, ready-to-implement. Its code-reviewer readscheduler.tsas confirming the mechanism, withreleaseSlot()in afinallythat does not run until the job’s promise settles: “Five hung jobs = five permanently occupied slots = everything else waits forever.” The fix it named is aPromise.racetimeout inrunJob, with a queue-depth cap as secondary hardening; the triage’s other expert recommended pulling/internal/jobsfirst to identify the stuck slots. The fix merged at 17:09 UTC on September 16, 2026 as #7556, opened under the maintainer account, and #7546 closed as completed one second later. It is thePromise.racetimeout the triage named, made opt-in: a scheduled job can setexecutionTimeoutMs, and when that expires the scheduler signals the run to stop through anAbortSignaland stops waiting on it, so thefinallyholdingreleaseSlot()runs even if the job never settles. Only the compliance heartbeat opts in, at two hours against a documented worst case of about 115 minutes; it passes the signal into the SDK run, checks it before each agent, and, when an aborted run throws, stops before recording a failure against the agent it was testing. The job status behind/internal/jobsnow reportsactiveJobs,queuedJobsandmaxConcurrency; neither this PR nor its 3.1.x backport adds the queue-depth cap. The repository’s Deploy workflow reports a successful Fly.io deploy of the merge commit, completed at 17:38 UTC, and two more ofmainthat include it, completed at 17:55 and 21:05 UTC. The 3.1.x backport, #7557, merged at 19:05 UTC the same day and carries over the guardmainalready had against a job overlapping its own previous run. The 35 public compliance feeds list only complete, authoritative runs, and since #7453 a timed-out run is kept as audit evidence without changing the public grade; the heartbeat also tests crawler-discovered agents outside the public listing. So the feeds bound the heartbeats that finished on these 35 agents, not the ones attempted or the ones run on other agents. In them, after one heartbeat on September 15, two appear on the 16th, at 18:19 and 18:23 UTC, on two agents, neither of them mine, and none after those through 12:46 UTC on September 17; the feeds do not show whether those two runs owe anything to the fix. Of the 31 agents with a heartbeat on record, 29 are more than two days past their last one. In those feeds my two agents have no heartbeat since the merge: their last were at 20:03 UTC on September 14 (signals) and 02:43 UTC on September 13 (sales), and their newest runs are owner-triggered, at 09:52 UTC on September 16 and 11:41 UTC on September 17. A follow-up on #7546 at 11:01 UTC on September 17, 2026 set out these public numbers with the same caveat and asked what the jobs status endpoint shows. - A field the 3.1 schema requires and two governance storyboards never send — on the 3.1 line,
governance_approvedandgovernance_conditionsregister a governance agent on the account, then callcreate_media_buywithoutplan_id, which the request schema from 3.1.20 through v3.1.23 says is “Required when the account hasgovernance_agents”. The seller has no other key for the approval call: the 3.1check_governancerequiresplan_idand tells governance agents to reject a siblingaccountfield (#7547, filed September 15, 2026). The sales agent shipped delegated governance that day —sync_governance, acheck_governancecall before commit,governance_aware: true— and a registration on the shared sandbox test account is permanent, so everycreate_media_buyon that account started to requireplan_id. Six storyboards went red: the two named here; the basemedia_buy_sellerstoryboard, which registers an agent in its own setup phase and then buys withoutplan_id; and three that never ask for governance and only reuse the account. The full local suite (@adcp/sdk13.0.4, bundle 3.1.20) read 246 steps passed and 8 failed, with Reporting & Delivery down to 7 of 17 scenarios. The filing argues that an agent declaringgovernance_awarecannot accept such a buy and skip the call. About an hour and a half after shipping, this one stopped declaring it. One constant,GOVERNANCE_DELEGATION_ENABLED = false, turns off the call and the tool:sync_governanceis offtools/listand refused withUNSUPPORTED_FEATURE. A test fails ifgovernance_aware, nowfalse, disagrees with the constant. A buy on the registered account now goes through withoutplan_idand without calling the agent, then waits for human approval like every buy on this agent. The suite went to 259 passed and 2 failed (post_sivtandsync_creatives_contradicted, both intentional). Bot-triaged P0 on September 15, at first with two options put to the maintainer: A, addplan_id: "$context.plan_id"to both storyboards; B, movemain’s deprecation ofplan_id, in favour ofgovernance_context, into the next 3.1.x patch. Our follow-up, under an hour after the switch-off, checked A against the shipped 3.1.20 bundle. Each file there has onecontext_outputsblock, owned byget_products_brief, so$context.plan_idis undefined. Each also has async_plansstep, a governance-agent task, without therequires: [multi_agent],default_agent: salesand per-stepagent:routing thatgovernance_deniedandgovernance_denied_recoverycarry. Sosync_plansis routed to the seller, and a seller-only agent skips the plan-setup phase as a missing tool. Both findings are about the 3.1 line. That follow-up, at 01:07 UTC on September 16, did not say so, and a second one at 10:21 UTC the same day did. Onmain, both storyboards have routedsync_plansto a governance agent and exported itsplan_idsince August 8, 2026 (PR #6277, PR #6280), and they sendgovernance_contextwhere the released 3.1 files send neither field — there$context.plan_idis defined, as the triage said. When that second follow-up went up, none of that had reached the 3.1.x branch or v3.1.23, the hosted card graded on bundle 3.1.20, and a 3.1 seller needed the fixes from #6277 and #6280 on 3.1.x under either option. At 10:26 UTC on September 16, 2026 the bot rewrote that triage. It now says the A-or-B framing rested on the 3.1.20 bundle alone, names a backport of #6277 and #6280 as the fix, and leaves the deprecation as a separate policy call. As of 11:25 UTC on September 17, 2026, nobody else has commented on #7547. At 17:09 UTC on September 16, 2026 PR #7555 merged into the 3.1.x branch. It marks the two 3.1 governance storyboardsrequires: [multi_agent]withdefault_agent: sales, routessync_plansto the governance agent and every other step to the seller, givessync_plansacontext_outputsblock that capturesplans[0].plan_id, sends that asplan_id: "$context.plan_id"oncreate_media_buy, and adds a test for the routing and the hand-off. It does not take #6277 and #6280 whole: its description says doing so would also import 3.2-only response fields such ascheck_typeandconsultation_context, which the 3.1 schema-path lints reject, so it carries only the routing andplan_idfixes. The 3.1.xcreate_media_buyrequest schema defines nogovernance_contextfield, and neither patched request sends one. The description says “Closes #7547”, but GitHub acts on that keyword only when a PR merges into the default branch,main, and as of 10:48 UTC on September 17, 2026, #7547 is still open. Nor is the fix released: there is no v3.1.24 tag, the 3.1.x branch is three commits past v3.1.23, and the open release PR (PR #7559) lists the change under[email protected]. The newest public run on the hosted card, an owner refresh at 09:55 UTC on September 16, 2026, before the merge, graded bundle 3.1.20. Of the storyboards, #7555 touches onlygovernance_approvedandgovernance_conditions. The other four that failed are unchanged on 3.1.x since v3.1.20, and each still callscreate_media_buyfor the brand and operator the governance storyboards register, withoutplan_id. Three of them,delivery_reporting,measurement_accountabilityandvendor_metric_accountability, register no agent and only reuse the account. The fourth, the basemedia_buy_sellerstoryboard, registers one in its owngovernance_setupphase, with no multi-agent requirement, and then buys withoutplan_id: the shape this filing reported, in a storyboard it did not name. Onmainits copy has the same shape and sends nogovernance_context, whichmainrequires once governance applies to the account. #7555 changes none of the four: turning the constant back on would again have the agent refuse those buys for lack ofplan_id, as it did in the local suite before the switch-off — the three while the registration on the shared account stands, andmedia_buy_selleron every run, since its own setup phase registers the agent again. The local suite cannot run the patched storyboards yet: it runs bundle 3.1.20 from@adcp/sdk13.0.4, which carries no later 3.1 bundle. The constant stays off. A follow-up on #7547 at 12:18 UTC on September 17, 2026 raised the base storyboard there, marked as outside that issue’s scope, with the evidence for both branches, and offered to file it separately. The maintainer account answered on September 18, 2026 to file it separately — the base storyboard is a different file, carries none of theplan_idinfrastructure the backport relies on, and has three plausible fix shapes of differing blast radius, so it should not absorb into a P0 thread otherwise ready to close. It is #7585, filed the same day with the per-branch line references: on 3.1.x the file has zero occurrences ofplan_id,sync_plans,multi_agentordefault_agent, and onmainzero ofgovernance_context. The filing concedes what the defect is not: on both branches the obligation is prose in the field description rather than a machine-enforced conditional — top-levelrequiredis the same five fields on each line, and neither schema carries anif/thenordependentRequiredtouching either field — so a schema-validation pass over the storyboard is clean, and it fails only against a seller that enforces the documented requirement. The timing matters because #7559’sdist/compliance/3.1.24copy is the identical blob, so 3.1.24 ships the storyboard unchanged unless the decision lands first. - A conformance failure you cannot act on —
capability_discoveryfailures reported the scenario name and nothing else: no request, no response, no pointer, no expected against actual (#2352, with the Signals track reported separately as #2356 when a declaredsignalsprotocol was told the track was not applicable). The maintainer wrote the protocol-side runner-output contract in response — #2364 mandates the exact request and response, an RFC 6901json_pointer, machine-readable expected and actual, a fetchableschema_urland six canonical skip reasons — and #2365 populated the signals baseline, which had shipped as a placeholder with no phases, so there had been nothing to run. Both merged the same day. Worth recording what the same thread established about us: the filing argued our agent was correctly implemented, and two of the failures were real non-conformances on our side, a missingadcp.idempotencyblock and nocontext.correlation_idecho. We fixed those. The contribution is the report and the reproduction; the implementation was the maintainer’s. - A second filing withdrawn — an argument that npm-published compliance caches omit the sibling
schemas/directory the resolver needs, leaving an earlier fix inert for anyone on a plainnpm install(#6650, re-filed correctly as adcp-client#2587). The maintainer unpacked the exact tarball and showed the resolver falling through to the package’s own bundled schemas, no sibling required. Retracted the same afternoon. The advisory that prompted it had a different cause entirely — a second, 3.0-shaped validation from the new mixed-version compatibility check — filed separately and fixed within the hour. Two things this one is worth remembering for: the issue still reads completed in the API, because an author self-close with no reason renders that way, and the automated triage posted a confirmed-and-ready-to-implement verdict at P1 four minutes after the issue was closed and under an hour before the human maintainer disproved it. Triage labels on this repository are a bot’s opinion, not a maintainer’s endorsement. - A storyboard that asks for a value the schema rejects, still shipping on 3.1 — the version-negotiation steps in
universal/error-compliance.yamlanderror-compliance-signals.yamltell a seller to answerVERSION_UNSUPPORTEDwithrecovery: fatal, and list the vocabulary as “correctable, transient, or fatal”.core/error.jsontypesrecoveryas["transient", "correctable", "terminal"], sofatalis not a legal value, andenums/error-code.jsonclasses this codecorrectable: re-pin to a supported release and retry. Someone else had already found it and filed it (#7375, September 8, 2026); PR #7376 corrected both lines onmainthe same day and the issue closed. What had not happened was the backport. On September 17, 2026 the 3.1.x source still carried the old prose at lines 302 and 359, and v3.1.23 — published on September 14, six days after the fix landed onmain— shipped it indist/compliance/3.1.23/. That is the line the hosted grader runs, on bundle 3.1.20. A comment on the closed issue that day set out the two branch states, the release timeline and the enum, and noted the grade is unaffected either way, because nothing in the SDK parses theexpected:prose — only thevalidations:entries decide pass or fail. Both of this site’s agents had the defect the prose would have caused, in opposite directions: the sales agent answered an unsupported pin withrecovery: "terminal", which on 3.1 means the buyer needs a human, and the signals adaptor sent norecoveryat all, where 3.1 says senders SHOULD populate it on every error. Both now answercorrectable, verified live. Seven minutes after that comment, PR #7572 was opened against3.1.xcarrying the same two-line change plus a changeset, described as ready for the 3.1.24 window. Like the other triages on this page it is bot-drafted under the maintainer account, so it still has to pass review and catch the release. The audit that followed on this side found the defect was a class rather than an instance: the sales agent’sToolErrorconstructor defaultsrecoverytoterminal, and its own doc comment named only two of the three legal values, so 16 sites across seven codes inherited the most severe classification for errors a buyer could have fixed — a wrong media buy id, a malformed request, a governance denial explained as “over budget”. Each was re-judged against what its call site actually does, and three were keptterminalon the evidence: one internal invariant on an id the buyer never supplies, and two admin-console paths where the field never reaches the wire. Those three are now pinned explicitly rather than inherited. The denial case also now forwards the governance agent’sfindings, whichcheck-governance-responserequires when the verdict is denied: calling an error correctable while dropping the reason leaves the buyer guessing what to change. - A filing that was right about the symptom and wrong about the cause — the signals agent’s hosted card showed
error_handlingat 10 of 11 scenarios. The failure was not an error-handling defect at all:billing_gate_dispatchassertsfield_present: account.supported_billingin an ungated first phase, and that field isrequiredwithminItems: 1, so there is no honest value for an agent with no account management. Its own description says buyers pass one of those values insync_accounts— a tool this agent does not expose. Declaring it to pass would have been thegovernance_awaremistake again. Filed as #7573 on September 18, 2026, bot-triaged P1 within eight minutes with a primary fix in the SDK and a defensive one in the storyboard. Two corrections followed, both ours. One line of the filing said the divergence only appeared when a track was forced; that was true of SDK 13.0.2 and false of the pinned 13.0.4, carried forward without re-checking. The larger one: the triage’s primary fix was a no-op. The runner already enforcedrequired_toolsin every mode — atcomply.js:600for capability-driven selection and again atrunner.js:1420at load time — as an any-of gate, which the bundled storyboard schema documents as deliberate and pairs with a strictrequired_any_of_toolsform that almost nothing uses. Both storyboards were admitted on one matching tool:get_adcp_capabilities, which every agent has, andcomply_test_controller. Searching only this protocol’s repository before filing had also missed two earlier filings of ours in the client repository, making this the third occurrence — and the any-of convergence that admits these storyboards shipped in 13.0.4 because we asked for it. The fix that landed, PR #7581, took the defensive route on September 18, 2026:optional: trueplusrequires_capability: { path: account, present: true }, with a runner test. It is onmainonly. The finding that mattered came afterwards. Therequired_toolscorrections had already shipped upstream — across storyboards on August 23 and for the other file on September 14 — and are in the released v3.1.23 bundle. Running the SDK’s ownresolveStoryboardsForCapabilitiesandpartitionStoryboardsByRequiredToolsagainst each bundle in turn: on 3.1.20 both storyboards are runnable, on 3.1.23 both are excluded for missingsync_accountsandvalidate_inputrespectively — 19 runnable storyboards and 171 steps down to 17 and 149, with nothing newly selected. So the storyboard defect had largely been fixed weeks earlier, and what remains is that the hosted grader is pinned to bundle 3.1.20. That is a registry-side pin, not a protocol matter, and it was raised there rather than on the issue. Excluded is not passed: those storyboards leave the denominator as not-applicable rather than becoming passing steps. - An idempotency key that dies with the row it protects — the recovery audit left one item out of scope:
buyResponseanswers a missed read-back withMEDIA_BUY_NOT_FOUND, on an id every caller derives server-side, so there is nothing in the request for the buyer to verify. The trigger is a console delete landing between the caller’s own read and that one. D1 has no interactive transactions and the purge’sbatch()is atomic only with itself, so nothing serialises the two. Three interleavings reproduce. The narrowest is the create path, since a fresh buy is writtenpending_approval, which sits in neither delete guard and so needs a decide-then-purge pair inside the window; the widest isget_media_buys, where one purged row fails the entire list and the sandbox sweep in the promotion cron carries no status guard at all. The first fix answeredCONFLICTwithrecovery: transientand was wrong.core/error.jsondefinestransientas “retry after delay”, the purge takes the row’sUNIQUEidempotency_keywith it, and a buyer following that instruction writes a second buy instead of replaying the first. Checked against the running implementation rather than argued: the same key after a purge returns a newmedia_buy_id. Create now answersterminalon both of its call sites, and the read paths staytransient, where a retry does converge. The half the spec owns went upstream (#7575, filed September 17, 2026):replay_ttl_secondsscopes the replay window purely in time, minimum 3600, and enumerates three outcomes, being the cached response,IDEMPOTENCY_CONFLICTandIDEMPOTENCY_EXPIRED. None of them is the fourth that any seller keying idempotency to the resource row will produce, where the key dies with the resource and a replay inside the advertised window creates a second one and returns success.CONFLICTis no better a fit: the schema scopes it to revision mismatch, anderror-details/conflict.jsoncarriesexpected_versionandcurrent_version, neither of which a deleted row has. - A test-scenario report that was out of date when filed — reported on April 27, 2026 that the SDK’s signals test scenario (
scenarios/signals.js) calledactivate_signalwithsignal_id, carrying aSignalIDobject, where the schema requires asignal_agent_segment_idstring (#3349, filed in the protocol repository under a header that already namedadcp-clientas its subproject, and re-filed there three minutes later as adcp-client#1033). The field name had been corrected on March 21, 2026 and released that day in@adcp/client4.13.0, five weeks before the filing. The object half was never true: even before that fix, the value passed was a string. The two bot triages disagreed: the one on #3349 accepted the premise, and the one on adcp-client#1033, fifteen minutes later, found the report false against the then-current 5.21.1. The client-repo copy was closed on April 30, 2026 as already fixed. This one was closed as not planned on May 19, 2026, with a comment that the spec storyboards already use the correct fields and the report belongs toadcp-client. Two merged PRs came out of the bot triages, and neither is the fix the filing asked for: the #3349 triage, taking the report at its word, drafted the removal of training-agent aliases that still accepted the old field names, with sharper schema descriptions (PR #3356, merged May 14, 2026), and the adcp-client#1033 triage drafted a fix for a separate bug it found in the same scenario, a governance advisory check that never fired (adcp-client#1036, merged April 28, 2026).
Open proposals
- Geo cell interoperability — conversion (#6013) and cell-grain delivery reporting (#6014) companions to the geo_cells RFC (#5969), with Nathan Woodman.
- Static OOH: the channel with no play event — static out-of-home has nothing to emit an impression against, so proof-of-posting has no shape in the delivery contract; filed with a measurement shape and posting-record proposal (#6140). The channel contract it asked for merged —
ooh_metrics, posting records and a channel guide (PR #6241) — and the measurement-shape half is still open. - A seller shape the taxonomy has no slot for — the specialisms treat guaranteed-vs-non-guaranteed as a proxy for human-approval-vs-auction, so a publisher-direct seller running fixed-price, non-guaranteed inventory behind a human approval gate matches neither:
sales-non-guaranteedassumes bidding and states that buys need no human approval, whilesales-guaranteeddescribes the human gate but assumes IO-signed reserved inventory and proposal flows (#6303, filed with our own live agent as the reproduction after withdrawing a claim we couldn't honestly certify). The certification contradiction found alongside it is already fixed: the docs stated that grading neither requires nor usescomply_test_controller, while specialism certification requiresseed_*scenarios only that controller can execute — confirmed and scoped to the storyboards it actually covers, since the exemption holds for universal and protocol runs but not for specialisms that seed state (#6301, fixed). - Eligibility you can read off the API —
verifiedis a bare boolean computed from badge rows, so a seller cannot tell “declare specialisms” from “fix storyboards” from “membership tier” (#6374, split from #6363 because the bot triage there flagged it as a separate API-contract issue, auto-triaged ready-to-implement within the hour, and untouched by a maintainer since — closes the API half of #3525): proposes a per-roleeligibilityblock with machine-readable blocker codes, filed with our agent as the reproduction — zero failing storyboards,verified: false, and the actual first-guard blocker (no declared specialisms, the #6303 taxonomy gap) surfaced by no public field. - revoke_activation revocation primitive (#4203) · Measurement Feedback Signal (#4296).
- JWKS adoption baseline — the chain-of-custody gate on signal-payment work (#4206).
Not protocol work — bug reports and a feature request against the AAO platform
- Shipped as filed, one of them only in part — the member profile edit page threw
parsed is not definedon load, a one-line scoping error (#4257), and photo uploads on the same page reverted silently on save, from two compounding causes: the upload handler wrote a base64 data URL the brand-identity endpoint rejects, and the save never sent the photo field (#4260). Both were fixed in PR #4352, merged May 10, 2026, which references neither issue; its description credits my in-app escalations of the same two defects, and both issues were closed on May 12 with a comment pointing to it. Badge links of the form/registry/agents/{encoded-url}returnedCannot GET(#5485) until PR #5492, a five-issue batch merged June 12, 2026, served that path as a public agent profile page. A “0 / 0 Community seats” row on a Professional plan, which the filing itself called display-only (#5494), was hidden on plans without Community seats by a small conditional (PR #5586, merged June 18, 2026). A copy button for replies in AAO’s Addie assistant shipped (#4266 → PR #4269) without the plain-text/markdown toggle the request also asked for, which the bot triage declined and the PR left out of scope. - Wrong or partial diagnoses, and one wrong report — the dashboard’s Test view listed media-buy storyboards for a signals-only agent; filed as missing capability filtering, but the filter already existed, and a fallback to the full catalog, taken when the platform’s capability probe of my agent failed for a reason the record does not give, bypassed it. The fix drops the fallback and shows the probe error, with a pointer to what
get_adcp_capabilitiesmust return, rather than the filtered list the filing expected (#4254 → PR #4369). The member directory showed “Tools: 0” for an agent with eight tools; filed as a stale comply cache, a theory the fixing PR calls incorrect: the public discovery endpoint probed the agent live and left the tool list out of its response (#4256 → PR #4261). The organization page offered “Register an agent” to an org that had one; the filing guessed the page was not querying the registry or was filtering on a stale comply cache, and the health service in fact returned a hard-coded agent count of zero (#4255 → PR #4258). Listing the org’s agents on that page, the filing’s first expected behaviour and its whole suggested fix, was left out of scope by the bot triage and the PR. Addie kept reporting GitHub as unconnected for a turn after OAuth completed; the bot triage and the fixing PR found the token lookup already ran live on every tool call, which rules out the filing’s caching guesses, and put the lag down to propagation delay in the upstream WorkOS API, which none of the filing’s three guesses named. The fix is a single 1.5-second retry that absorbs that window, a note in the not-connected reply that the connection may still be propagating, and a prompt rule that stops Addie offering the Connect link again right after the user says it is connected; none of it is a change to connection state (#4348 → PR #4349). And one report was wrong. A community profile photo picker, on a different page from #4260, reported as broken, was the reference-photo input for AI portrait generation: the filing’s own steps name it (“Upload a reference photo”), and the bot triage found its hint reads “Used as reference only, not stored” and that the avatar changes only after Generate and Approve. That triage put a choice between a direct upload path and clearer labels to the maintainer, and the merged fix built the upload path (#4658 → PR #5157, merged May 29, 2026). - Registry & docs reliability — registry UI rendering empty product/format sections for anonymous viewers despite working public endpoints (#6229, fixed and closed; my own duplicate of it, #6230, filed 57 seconds later, drew a bot triage the same day that surfaced a third break in publisher discovery; I carried that finding across when I closed #6230 as a duplicate on August 6, 2026, which GitHub records as not planned, and it was fixed in the same change) and the dashboard's aao-verified doc links returning 404 (#6231, fixed and closed). The registry card's "Publishers & properties" panel surfacing a raw JSON-parse exception instead of the actual authorization-list cause (#7333) is fixed too — PR #7362 now indexes publisher authorization data, guards invalid JSON responses, and renders clean 404/error states.
Contribution statuses validated September 16, 2026 against the public AdCP repository; open proposals are point-in-time and may merge, change, or close. Maintainer-authored work is credited as review influence, not authorship.
Protocol domains.
AdCP organizes work into domain-specific tasks and schemas. Task names below are drawn from current official documentation.
-
Media Buy
Inventory discovery, campaign creation, delivery reporting.
get_products · create_media_buy · get_media_buy_delivery -
Creative
Creative format discovery, building, preview, distribution.
build_creative · preview_creative · list_creative_formats -
Signals
Audience and contextual data discovery and activation.
get_signals · activate_signal -
Accounts
Commercial identity, billing, and usage reporting.
list_accounts · sync_accounts · report_usage -
Governance
Brand suitability, approval, policy, audit, content standards.
create_content_standards · calibrate_content -
Brand
Machine-readable brand identity and authorized agents via brand.json.
brand.json discovery -
Sponsored Intelligence
Conversational brand experiences on AI platforms.
Experimental surface in AdCP 3.0 -
Curation
Media inventory curation.
Listed "coming soon" — not a current domain
From brief to governed execution.
- Discover authorized agents — adagents.json, brand.json, registry
- Set up account context — commercial identity, billing, advertiser / operator relationship
- Discover inventory and products — natural-language brief into structured product responses
- Build or sync creative — formats, assets, brand identity, approvals
- Add signals — audiences, suppression, contextual signals, signal activation
- Check governance — budget, brand safety, targeting rules, approvals
- Execute, report, and audit — media buy, delivery, events, audit logs
Discovery starts from two machine-readable files — a publisher's adagents.json (who may sell its inventory) and a brand's brand.json (who may act for the brand, and how):
{
"publisher": "example-publisher.com",
"authorized_agents": [
{
"url": "https://sales-agent.example-ssp.com",
"role": "seller",
"properties": ["news", "sports"]
}
],
"signing_keys": [
{ "kid": "2026-key-1", "alg": "Ed25519" }
]
} {
"brand": "example-brand.com",
"display_name": "Example Brand",
"authorized_agents": [
{ "url": "https://agent.example-buyer.com", "role": "buyer" }
],
"identity": {
"logos": ["/brand/logo.svg"],
"colors": ["#1a1a1a", "var(--accent)"],
"tone": "confident, plain-spoken"
},
"policy": { "restricted_categories": ["gambling", "politics"] }
} Signals: where AdCP meets signal containerization.
The signals layer is where AdCP becomes especially important for agentic advertising. Agents need to discover signals, understand what they mean, check provenance and policy, activate them, and monitor status. That is the same operating problem behind signal containerization — turning audience logic into a governed, executable object.
Discover
What signals exist and what do they mean?
Inspect
Where did the signal come from, how fresh is it, and what policy applies?
Activate
Where can the signal run: seller, platform, identity path, contextual path, or measurement layer?
Monitor
What is the activation status and what outcome did it improve?
An agent discovers signals with a natural-language brief, then activates the chosen signal to a destination — a single get_signals call rather than a taxonomy lookup:
{
"tool": "get_signals",
"arguments": {
"brief": "in-market for premium CTV, US households, privacy-safe",
"deliver_to": { "platform": "dsp", "account": "acct_123" },
"max_results": 10
}
} One open question now sits under this layer. The vectors agents exchange today are text-family embeddings — they encode the words that describe an audience or a brief, and text models famously lose numbers, geography, and time. A second family, spatiotemporal embeddings, encodes the place and hour themselves. A text space and a place-time space do not share a geometry: similarity across the two is undefined until someone defines it — a signals-standards question, exactly the kind of definition a signals working group exists to produce, not an integration ticket.
Governance is the trust layer.
Agentic advertising only works if actions are accountable. A protocol needs to know who called a tool, what authority they had, what was approved, what conditions applied, and what actually ran.
- Human-in-the-loop approvals (a three-party separation of duties)
- Idempotency keys on every mutating request
- Request signing (RFC 9421 HTTP Message Signatures)
- Correlated audit logs
- Governance checks and signed governance tokens
- Brand-safety and content-standard enforcement
- Budget thresholds and authority / role boundaries
The principle
Human judgment is embedded in the system design, not bolted on afterward — the human stays the locus of accountability.
Where AdCP fits with IAB Tech Lab standards.
AdCP is best understood as an agentic management and workflow layer. IAB Tech Lab's AAMP work extends existing standards with agentic foundations, protocols, and trust infrastructure — and its buyer and seller reference agents do workflow-layer work of their own (briefs, planning, negotiation, OpenDirect booking); ARTF focuses on containerized real-time execution; Agentic Audiences on embedding-based signal exchange. These layers can be complementary with clear boundaries — the full repository-level comparison, verified against both codebases, is in AdCP vs AAMP: The Retrofit and the Greenfield. See also the IAB Agentic Standards guide. The protocol's sell side already has a shipping open-source implementation — Prebid.org took stewardship of AdCP's sell-side reference code in January 2026, dissected in The Prebid Sales Agent — and landing inside the publisher execution layer documented in Prebid & Header Bidding Infrastructure.
| Initiative | Role | Best question |
|---|---|---|
| AdCP | Agent task / workflow language | What should the agent ask or do? |
| AAMP | IAB Tech Lab umbrella — buyer/seller reference agents plus foundations and trust | How do agents run briefs, negotiation and booking on the standards already in production? |
| ARTF | Containerized real-time execution framework | How can service agents run inside host infrastructure with low latency and controlled mutation? |
| Agentic Audiences | Embedding-based signal exchange | How do agents exchange identity, contextual, and reinforcement signals? |
| OpenRTB / AdCOM / Deals API / OpenDirect | Existing transaction and object standards | What underlying objects and transaction models should agentic systems reference? |
Implementation lens by company type.
Pick your company type to see what to expose or consume — and the AdCP domains it touches first.
Authorized agents, inventory products, packages, account setup, governance, reporting.
Partner discovery, product discovery, creative formats, media buy, governance, reporting.
Signal catalog, provenance, activation paths, pricing, policy, status.
Seller agents, deals, signals, governance, a real-time execution bridge, reporting.
Buyer agents, media buys, creative, signal activation, governance, measurement.
Formats, asset generation, preview, brand identity, approval workflow.
Delivery, event logging, attribution, audit, lift, governed outputs.
A protocol defines the task. It does not replace judgment.
The industry should avoid treating AdCP as a magic layer that makes agents safe by default. The protocol can define the task. It cannot replace commercial judgment, governance design, data rights, measurement discipline, or operating ownership.
- Start with one workflow, not every workflow.
- Expose the safest useful task first.
- Design governance before spend movement.
- Treat signals as governed objects, not just segment IDs.
- Build audit and status from day one.
- Connect protocol work to business outcomes, not demos.
Where this fits in the full standards stack.
The layer map
AdCP defines the agent workflow layer. It still needs transaction rails, privacy constraints, measurement trust, and evidence discipline around it.
Building around AdCP or agentic advertising standards?
Use the playbooks to map the workflow, governance, signal model, implementation path, and commercial productization before building against the spec.