You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
π§ DO NOT TRIAGE β tracking / positioning issue, owned by @bokelley. Work is decomposed across child issues #477, #478, #479. Do not assign work directly here.
Why this issue exists
When an AdCP operator sits down to build a multi-tenant sales agent today, the natural design instinct is to define an AdServerAdapter ABC β a contract their adapters (GAM, Kevel, Triton, in-house) implement, with a registry that dispatches per tenant. We've now seen this pattern emerge independently in at least one mature downstream (~15K LOC of Layer-2 infrastructure built around a custom AdServerAdapter).
The SDK already ships the contract: adcp.decisioning.DecisioningPlatform + specialism mixins (SalesPlatform, SignalsPlatform, CreativePlatform, GovernancePlatform, etc.) + AccountStore + BuyerAgentRegistry + TaskRegistry + webhook_emit + dispatch + compose. Operators who write their own AdServerAdapter ABC are building a parallel implementation of DecisioningPlatform.
This isn't anyone's fault. The SDK has historically been positioned as "protocol runtime + types," not "the canonical adapter contract." Operators who want a multi-tenant adapter pattern have to dig into adcp.decisioning and infer the seam from the reference seller, which assumes one platform per process.
This tracking issue captures the strategic alignment work: make DecisioningPlatform the obvious answer to "what's the AdCP adapter contract?", with worked examples for the multi-platform case and migration patterns for operators with parallel abstractions.
Scope (constituent issues)
The actual work is decomposed into three child issues:
feat(decisioning): project full get_adcp_capabilities response schema into DecisioningCapabilitiesΒ #479 β Capabilities projection. Project full get_adcp_capabilities response schema into DecisioningCapabilities (currently 7 fields, spec defines ~25 across 13 blocks). Removes the need for adopters to override get_adcp_capabilities with hand-built dicts. Makes DecisioningCapabilities a complete declarative surface.
Building a codemod from any specific operator's adapter shape to DecisioningPlatform. Operators are too divergent to make a single codemod useful. Migration docs cover the patterns; codemod work is per-adopter and lives in their repos.
Inventory / product-catalog primitives as SDK seams. Latent β no current downstream blocking on them. Defer until a second sales-agent operator validates demand.
Adopter-specific migration PRs. Those are downstream work in adopter repos, not SDK work. We supply patterns and worked examples; adopters choose if and when to migrate.
docs/decisioning/adapter-contract.md documents the canonical contract and links the worked examples
At least one external downstream has either (a) migrated to DecisioningPlatform against the new patterns, or (b) provided concrete feedback on what's still missing
The SDK README / landing docs surface "build an AdCP adapter" as a first-class workflow with a clear entry point
Why this matters strategically
Three downstream effects, in order of immediacy:
Reduces duplicate-implementation cost for operators. Every operator writing their own AdServerAdapter is rebuilding what DecisioningPlatform already does β usually with worse multi-tenant ergonomics, less spec coverage, and webhook delivery bugs the SDK has already solved (signed-bytes-vs-sent-bytes, retry/circuit-breaker, durable queue).
Establishes the marketplace seam. Once DecisioningPlatform is the obvious answer, third parties (publishers, SSPs, vendors) can ship adapter packages against a stable contract. Today there's no contract to ship against β every adapter is monorepo-bound.
Aligns SDK positioning with AAO mission. The SDK shifts from "Python types + protocol runtime" to "the runtime everyone uses to build AdCP-conformant adapters." That's the ecosystem play; the protocol-library framing leaves the high-leverage layer (Layer 2) unowned and re-implemented N times.
Open positioning questions (no decision needed yet)
Should adcp.adapters exist as a package namespace? Today everything platform-shaped lives under adcp.decisioning. "Adapter" might be more discoverable for operators arriving from a multi-tenant-SaaS background; "decisioning platform" is more accurate to the AdCP spec. Possible to ship adcp.adapters as an alias namespace re-exporting adcp.decisioning symbols.
This issue is positioning, not engineering. Engineering happens in #477 / #478 / #479. Don't let this issue accumulate scope creep β the moment something concrete needs doing, it gets a child issue.
Why this issue exists
When an AdCP operator sits down to build a multi-tenant sales agent today, the natural design instinct is to define an
AdServerAdapterABC β a contract their adapters (GAM, Kevel, Triton, in-house) implement, with a registry that dispatches per tenant. We've now seen this pattern emerge independently in at least one mature downstream (~15K LOC of Layer-2 infrastructure built around a customAdServerAdapter).The SDK already ships the contract:
adcp.decisioning.DecisioningPlatform+ specialism mixins (SalesPlatform,SignalsPlatform,CreativePlatform,GovernancePlatform, etc.) +AccountStore+BuyerAgentRegistry+TaskRegistry+webhook_emit+dispatch+compose. Operators who write their ownAdServerAdapterABC are building a parallel implementation ofDecisioningPlatform.This isn't anyone's fault. The SDK has historically been positioned as "protocol runtime + types," not "the canonical adapter contract." Operators who want a multi-tenant adapter pattern have to dig into
adcp.decisioningand infer the seam from the reference seller, which assumes one platform per process.This tracking issue captures the strategic alignment work: make
DecisioningPlatformthe obvious answer to "what's the AdCP adapter contract?", with worked examples for the multi-platform case and migration patterns for operators with parallel abstractions.Scope (constituent issues)
The actual work is decomposed into three child issues:
feat(examples): multi-platform-per-process proof β mock platforms with PlatformRouter, refactor v3 reference seller along the wayΒ #477 β Multi-platform proof + ref seller refactor. Two pure-Python in-process mock platforms (
MockGuaranteedPlatform,MockNonGuaranteedPlatform) +PlatformRouterrecipe + storyboard validation across two tenants. Refactorsexamples/v3_reference_seller/to usePlatformRouteras the single composition pattern. The canonical worked example for multi-tenant adapter migration.feat(decisioning): project full get_adcp_capabilities response schema into DecisioningCapabilitiesΒ #479 β Capabilities projection. Project full
get_adcp_capabilitiesresponse schema intoDecisioningCapabilities(currently 7 fields, spec defines ~25 across 13 blocks). Removes the need for adopters to overrideget_adcp_capabilitieswith hand-built dicts. MakesDecisioningCapabilitiesa complete declarative surface.feat(webhooks): HMAC/bearer outbound delivery + Docker localhost normalization for parity with operator-shaped webhook sendersΒ #478 β Webhook delivery extensions. HMAC + bearer + Docker localhost normalization + Standard Webhooks scheme on the existing
WebhookSender/WebhookDeliverySupervisor. Closes the "operator-shaped legacy webhook" gap that pushes operators to roll their own.What's deliberately NOT in scope
PlatformRouterfrom example β SDK runtime (adcp.decisioning.dispatch.PlatformRouter). Wait until the example proves out under feat(examples): multi-platform-per-process proof β mock platforms with PlatformRouter, refactor v3 reference seller along the wayΒ #477, then file a follow-up.DecisioningPlatform. Operators are too divergent to make a single codemod useful. Migration docs cover the patterns; codemod work is per-adopter and lives in their repos.pip install adcp-<vendor>-adapter). Defer until feat(examples): multi-platform-per-process proof β mock platforms with PlatformRouter, refactor v3 reference seller along the wayΒ #477 proves the multi-platform pattern in practice. Premature standardization risks codifying the wrong shape.Success criteria
This tracking issue closes when:
docs/decisioning/adapter-contract.mddocuments the canonical contract and links the worked examplesDecisioningPlatformagainst the new patterns, or (b) provided concrete feedback on what's still missingWhy this matters strategically
Three downstream effects, in order of immediacy:
Reduces duplicate-implementation cost for operators. Every operator writing their own
AdServerAdapteris rebuilding whatDecisioningPlatformalready does β usually with worse multi-tenant ergonomics, less spec coverage, and webhook delivery bugs the SDK has already solved (signed-bytes-vs-sent-bytes, retry/circuit-breaker, durable queue).Establishes the marketplace seam. Once
DecisioningPlatformis the obvious answer, third parties (publishers, SSPs, vendors) can ship adapter packages against a stable contract. Today there's no contract to ship against β every adapter is monorepo-bound.Aligns SDK positioning with AAO mission. The SDK shifts from "Python types + protocol runtime" to "the runtime everyone uses to build AdCP-conformant adapters." That's the ecosystem play; the protocol-library framing leaves the high-leverage layer (Layer 2) unowned and re-implemented N times.
Open positioning questions (no decision needed yet)
Should
adcp.adaptersexist as a package namespace? Today everything platform-shaped lives underadcp.decisioning. "Adapter" might be more discoverable for operators arriving from a multi-tenant-SaaS background; "decisioning platform" is more accurate to the AdCP spec. Possible to shipadcp.adaptersas an alias namespace re-exportingadcp.decisioningsymbols.Should the SDK ship reference adapters? A Python-native mock adapter is built in feat(examples): multi-platform-per-process proof β mock platforms with PlatformRouter, refactor v3 reference seller along the wayΒ #477. Whether
adcp-<vendor>-adapterpackages are an SDK-org concern or community-owned projects is a positioning call we can defer.This issue is positioning, not engineering. Engineering happens in #477 / #478 / #479. Don't let this issue accumulate scope creep β the moment something concrete needs doing, it gets a child issue.