Problem
adcp 5.0 (PR #629, closes #614) shipped serve(pre_validation_hooks=...) — the mechanism is great, but every Python adopter that accepts pre-v3 / pre-4.4 buyers needs the same handful of spec-mandated default + shape-migration hooks. Each adopter writes (and maintains) the same code.
In bokelley/salesagent we just migrated our 273-LOC SpecDefaultsMiddleware to ~150 LOC of pre_validation_hooks. Looking at what we wrote, none of it is salesagent-specific — every line is a universal compatibility shim:
| Hook |
What it does |
Why universal |
get_products.buying_mode='brief' |
Default the field when omitted |
Spec says: "Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'." Every seller, by the spec text. |
sync_creatives.creatives[*].format_id string → {agent_url, id} |
Wrap bare string format_id as the structured 4.4 reference |
adcp 4.4 changed format_id from string to structured. Pre-4.4 buyers send a string; any seller accepting them needs the same wrap. The default agent_url is the canonical https://creative.adcontextprotocol.org/ registry. |
sync_creatives.creatives[*].assets[*].asset_type inference |
Infer the missing asset_type from the asset key (image/video/...) or from content/url/width+height presence |
adcp 4.4 added explicit asset_type to make AssetVariant discriminated; pre-4.4 buyers used the key as the implicit type. Universal compat. |
sync_creatives.creatives[*].assets[*] image → url demote |
When asset_type='image' but width/height missing, demote to url variant |
The strict 4.4 image variant requires dims; pre-4.4 buyers frequently send image-shaped URLs without them. Universal compat. |
We're carrying this code as if it were adopter logic. It's not — it's the SDK's compat layer for migrating buyers across spec major versions.
Proposed shape
Ship a built-in spec_compat_hooks() (or similarly named) in adcp.server that adopters can wire into pre_validation_hooks:
from adcp.server import spec_compat_hooks, serve
# Standalone:
serve(router, pre_validation_hooks=spec_compat_hooks())
# Combined with adopter-specific hooks:
serve(router, pre_validation_hooks={
**spec_compat_hooks(),
"create_media_buy": my_custom_hook,
})
# Selective opt-out:
serve(router, pre_validation_hooks=spec_compat_hooks(exclude={"sync_creatives"}))
Opt-in by design — adopters who want strict-4.4 rejection of pre-v3 / pre-4.4 buyers leave it off. Adopters who accept the long tail of legacy clients (most production sellers) wire it once and forget about it.
What's in / out of scope
In: The four hooks above. Each maps to a spec versioning event (pre-v3 buying_mode, pre-4.4 format_id shape, pre-4.4 asset_type discriminator, pre-4.4 image dim requirement).
Out: Adopter-specific defaulting (e.g. salesagent's removed account: auth-chain placeholder — that was an adopter bug, not a spec migration). Adopters write those themselves via the same pre_validation_hooks mechanism, layered on top.
Why this matters beyond us
These hooks are exactly the kind of glue every adopter has to write when migrating from a pre-4.4 SDK to a 5.0 SDK. Shipping them in the SDK:
- Saves each adopter ~150 LOC of mechanical-but-spec-sensitive code.
- Centralises the canonical agent_url (
https://creative.adcontextprotocol.org/), the asset-type inference table, the image-dim demotion rule — adopters who roll their own will silently diverge.
- Provides a single PR target when the spec adds the next pre-major-version compat shim (adcp 6.0 → 7.0 someday) instead of every adopter re-discovering it.
Source
Our core/spec_default_hooks.py is what we just wrote — happy to send the contents as a starter PR if there's interest in shipping it directly. The file is intentionally pure-Python (no transport / framework deps) so dropping it into adcp.server should be drop-in.
Related
Problem
adcp 5.0 (PR #629, closes #614) shipped
serve(pre_validation_hooks=...)— the mechanism is great, but every Python adopter that accepts pre-v3 / pre-4.4 buyers needs the same handful of spec-mandated default + shape-migration hooks. Each adopter writes (and maintains) the same code.In bokelley/salesagent we just migrated our 273-LOC
SpecDefaultsMiddlewareto ~150 LOC ofpre_validation_hooks. Looking at what we wrote, none of it is salesagent-specific — every line is a universal compatibility shim:get_products.buying_mode='brief'sync_creatives.creatives[*].format_idstring →{agent_url, id}agent_urlis the canonicalhttps://creative.adcontextprotocol.org/registry.sync_creatives.creatives[*].assets[*].asset_typeinferenceasset_typefrom the asset key (image/video/...) or fromcontent/url/width+heightpresenceasset_typeto makeAssetVariantdiscriminated; pre-4.4 buyers used the key as the implicit type. Universal compat.sync_creatives.creatives[*].assets[*]image → url demoteasset_type='image'butwidth/heightmissing, demote tourlvariantWe're carrying this code as if it were adopter logic. It's not — it's the SDK's compat layer for migrating buyers across spec major versions.
Proposed shape
Ship a built-in
spec_compat_hooks()(or similarly named) inadcp.serverthat adopters can wire intopre_validation_hooks:Opt-in by design — adopters who want strict-4.4 rejection of pre-v3 / pre-4.4 buyers leave it off. Adopters who accept the long tail of legacy clients (most production sellers) wire it once and forget about it.
What's in / out of scope
In: The four hooks above. Each maps to a spec versioning event (pre-v3
buying_mode, pre-4.4format_idshape, pre-4.4asset_typediscriminator, pre-4.4 image dim requirement).Out: Adopter-specific defaulting (e.g. salesagent's removed
account: auth-chainplaceholder — that was an adopter bug, not a spec migration). Adopters write those themselves via the samepre_validation_hooksmechanism, layered on top.Why this matters beyond us
These hooks are exactly the kind of glue every adopter has to write when migrating from a pre-4.4 SDK to a 5.0 SDK. Shipping them in the SDK:
https://creative.adcontextprotocol.org/), the asset-type inference table, the image-dim demotion rule — adopters who roll their own will silently diverge.Source
Our core/spec_default_hooks.py is what we just wrote — happy to send the contents as a starter PR if there's interest in shipping it directly. The file is intentionally pure-Python (no transport / framework deps) so dropping it into
adcp.servershould be drop-in.Related
pre_validation_hooksmechanism this builds on.accountis omitted on auth-resolved tools #623 — closed, established the spec-correctness boundary:accountandidempotency_keyare unconditionally required (NOT spec-migration defaults; adopters must enforce them). This issue stays on the right side of that boundary — only proposing hooks for fields the spec explicitly marks as defaultable or as shape-migration cases.