docs: salesagent → adcp Python SDK migration guide#489
Closed
bokelley wants to merge 8 commits into
Closed
Conversation
…lation guide Translation guide for adopters running salesagent-shaped multi-tenant adapter-registry code. Covers the move from `ADAPTER_REGISTRY` / `AdServerAdapter` ABC to `PlatformRouter` + `DecisioningPlatform` + `SalesPlatform`, with concrete file:line references into salesagent for each before-shape and shipping SDK primitives for each after-shape. Refs #477. Implementation lands in parallel PR `bokelley/feat-platform-router`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t sink, migration order - Add 'Foundations' section explaining how salesagent's monolithic Principal model maps onto two SDK lookups: BuyerAgentRegistry (agent identity) + AccountStore (account context). This is the JS-SDK-style separation Brian asked about. - Clarify the router's accounts= field is a single global AccountStore that reads from per-tenant Principal rows, not a per-tenant store. - Remove accounts = ... from the GAMPlatform example: in multi-platform mode behind a PlatformRouter, only the router declares accounts; child platforms work with ctx.account. - Audit logger: replace 'framework doesn't manage it' with reference to the SDK's AuditSink Protocol (LoggingAuditSink, SlackAlertSink reference impls). - Migration order: drop Kevel as the recommended starting point. Only GAM and Broadstreet have real client deployments; ~99% of clients are on GAM. - Diagram + section 3.1 example: replace KevelPlatform with BroadstreetPlatform to reflect actual deployment shape.
…lisms, and salesagent's AdCP 3.0 gaps The earlier draft framed the salesagent → SDK move as a 1:1 translation. That misrepresents the territory: salesagent today implements a subset of AdCP 3.0, and the migration is part-port, part-upgrade. Adds an honest "what this adds, not just translates" framing plus three new translation sections that were missing entirely: - §3.3 Product discovery and the refine flow — the get_products → create_media_buy seam (formalizing the implementation_config plumbing salesagent already does inline) plus the multi-turn refine flow that salesagent's request schema accepts but no adapter consults. - §3.4 Creative specialisms — splitting salesagent's single CreativeEngineAdapter.process_creatives surface across the two Platform Protocols (CreativeAdServerPlatform stateful library + tag gen, CreativeBuilderPlatform brief-to-creative), with CreativeBuilderPlatform flagged as greenfield. - §3.5 Signals — the structural shift from a global core/tools/signals.py tool body to per-tenant SignalsPlatform instances behind the router. Renumbers the existing 3.3–3.10 cross-cutting-concern sections to 3.6–3.13. Updates the migration order to add explicit phases for porting creative, moving signals to per-tenant platforms, and adding the refine handler, and pushes "stand up PlatformRouter" to the last step (each platform validates standalone first). Extends "What this doesn't solve" with the AdCP 3.0 surfaces that are gaps from the migration rather than flaws in the SDK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add §3.6 (reporting and delivery surfaces) covering the get_media_buy_delivery clean translation, get_creative_delivery greenfield, and capability-declared push reporting via webhook / offline bucket. Add §3.7 (governance specialisms) covering the three independently claimable Platform Protocols (BrandRightsPlatform, ContentStandardsPlatform, CampaignGovernancePlatform) and the recommended per-tenant adoption order. Update the translates / port-and-extend / greenfield buckets, the migration order (insert push-reporting + governance steps, renumbering 10-12 to 12-14), and "what this doesn't solve" to call out per-creative reporting and active governance as upstream-dependent / greenfield gaps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, adagents.json verification Add §3.8 covering property lists, collection lists, and adagents.json registry verification. Property lists translate from salesagent's global core/tools/properties.py into a per-tenant PropertyListsPlatform behind the router (same tool→platform shape change as signals); collection lists are greenfield (salesagent has no collection-list code); adagents.json fetch/verify infrastructure stays adopter-side, while the SDK formalizes the wire-level references so buyers can independently re-verify. Update bucket lists, migration order (steps 12-13), and "what this doesn't solve" accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lley feedback Four reframings on examples/multi_platform_seller/MIGRATION_FROM_ADAPTER_REGISTRY.md: - Foundations (§Principal mapping): add caveat that Principal schema split into BuyerAgent + Account is healthier long-term, even though wrap-today works. - §3.4 Creative: salesagent's current shape is fine for 3.0. AdCP creative is muddy at 3.0; SDK absorbs 3.0 → 3.1 translation. CreativeAdServerPlatform is upstream of sales agents in practice. Drop "port and extend" framing. - §3.5 Signals: salesagent's signals is a slightly different implementation, not wrong-shape. Open architectural question is whether SDK should grow inventory_store / signal_store primitives for dynamic-product assembly + key-value targeting threading. - §3.7 Governance: governance_agents is configuration declaring required governance enforcement, not decorative metadata. The governance-aware-seller lifecycle is unfinished on both sides — SDK seller-side check_governance wiring is "spec-recognized but unenforced," salesagent has the field but no enforcement code. Updates §3.2 bucket entries, migration-order steps 7 and 11, and the "What this doesn't solve" section to match. Net: -54 lines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 3, 2026
bokelley
added a commit
that referenced
this pull request
May 3, 2026
…composition (#502) * docs(proposals): product architecture — layered model + two-platform composition Foundational design doc capturing the layered architecture for products across the SDK: wire / internal-config (recipe) / capability overlap / supporting tables. Anchors the missing prerequisite that the salesagent migration guide (PR #489) currently has no answer for. Key conceptual moves: - Four-layer model with explicit ownership per layer. - Two-platform composition: ProposalManager + DecisioningPlatform, with the recipe (implementation_config) as the typed contract between them. - Three concrete shapes: tight coupling (LinkedIn), sophisticated multi-decisioning (Prebid salesagent), naive (programmatic non-guaranteed). - Path B recipe binding: discriminated-union with recipe_kind tag, explicit tenant binding, boot-time validation. - ProposalCapabilities is sales-axis-scoped (not generic to all specialisms) — guaranteed vs non-guaranteed flavors. Status: DRAFT (conceptual scaffold). Concrete examples sections marked [citations pending] for follow-up agent pass once budget allows. Python-first; ports back to JS once settled. Reverses the typical direction (lifecycle-state proposal went JS-to-Python) because the conceptual gap surfaced during the Python migration story. Refs: - PR #489 (migration guide reviewer feedback that prompted this) - Issues #491-#497 (already-filed buyer-side request-shape helpers) - #477 (multi-platform proof — interacts with tenant binding model) - proposals/decisioning-platform-dispatch-design.md (current DecisioningPlatform design) * docs(proposals): v1 ProposalManager is mock-backend forwarder, not new catalog impl Brian's review correction: the mock seller backend (bin/adcp.js mock-server) already implements the naive case via product fixtures. SDK doesn't need a separate SimpleCatalogProposalManager. v1 of ProposalManager is just the wiring that forwards to the mock backend — symmetric with DecisioningPlatform's upstream_for(ctx) mock-mode dispatch from Phase 2. Adopters declare a mock_upstream_url; framework forwards get_products / refine; recipes flow back. Updates: - Shape 3 reframed: Naive → Mock-backed (v1 default) - MockProposalManager sketch with parallel pattern to upstream_for - Independent-modes table: ProposalManager and DecisioningPlatform can each be mock or live independently - Future-issues list: SimpleCatalogProposalManager replaced with MockProposalManager (the v1 work is wiring, not a new impl) - §What ships in v1 framing: framework wiring, not catalog content * docs(proposals): address review comments — recipe lifecycle, hydration, finalize Four corrections from Brian's review: 1. Recipe lifecycle (Layer 2): never on the wire. Lives in framework session cache during negotiation; persists alongside the committed proposal after finalize; the framework hydrates it for every subsequent operation in the buy's lifecycle. Adopters do NOT store recipes themselves — the SDK is the system of record. 2. ProposalManager flavors: don't lock the formal taxonomy. Two extremes today (simple catalog vs complex proposal); useful variants will emerge between. sales_specialism + capability flags let adopters declare their actual shape; naming variants is future-state work. 3. Proposal hydration + capability validation upstream of adapter: buyer may reference packages by proposal_id OR product_id; framework hydrates from session cache or persisted store and validates the buyer's request against capability_overlap BEFORE invoking adapter. Buyer asking for geo-metro targeting on a product that doesn't expose it gets a structured error without adapter code participating. Layer 3 capability-overlap seam gets concrete. 4. Acceptance IS in the spec — buying_mode='refine' with refine[] action='finalize' transitions a draft proposal to committed with locked pricing, expires_at hold window, optional HITL. I incorrectly said the spec had no proposal lifecycle. Rewrote the section to walk the actual wire flow: get_products → refine → finalize → create_media_buy. SDK responsibilities at the seam: session cache, finalize transition, expires_at enforcement, persistence through the buy lifecycle.
Per #502's "Migration impact" directive: §3.3 introduces the proposal/decisioning seam adopters will split along long-term; §3.5 resolves the inventory_store/signal_store open question to "ProposalManager concern". §3.7 (governance) is unaffected by the split. Cross-link added to See also. Adopters land the migration on SalesPlatform today; ProposalManager arrives as a follow-up Protocol that splits the class along the recipe seam without re-porting either side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
bokelley
added a commit
that referenced
this pull request
May 4, 2026
Salesagent's multi-adapter abstraction is vestigial: GAM is the only deployed backend (~99% of clients per migration guide §"Migration order"); Kevel/Broadstreet/Triton/Xandr are scaffolding with no client traffic; mock is a fixture. Treating salesagent as a GAM agent that ships dead code simplifies the experiment in three concrete ways: 1. Wrap target is unconditional GAM. The if-adapter-class switches in _impl (e.g., media_buy_create.py:2431-2464) collapse to unconditional logic; no compatibility surface to preserve. 2. Single recipe type — salesagent contributes only the GAM shape to #502's typed-recipe model. Phase 1 falsification narrows. 3. MockAdServer (~1,800 LOC) deletion joins post-experiment cleanup. Updates: - New "Reframing" section after Two phases - Out of scope clarified: other adapters slated for deletion, not preserved - Next steps adds adapter deprecation roadmap (~3,500-4,000 LOC deletion across 4 sequenced PRs) and side-car-to-runtime promotion path - Note that #489 §3.1 needs a "single-adapter adopters skip PlatformRouter" addition (tracked separately) Doesn't change experiment shape: two-platform composition seam, recipe falsification target, HITL/webhook/auth shim work all stay. Reframing simplifies the success path; doesn't shrink the questions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PlatformRouter is the right primitive for heterogeneous platforms behind one process. Single-adapter adopters (salesagent today — GAM-only deployments, with Kevel/Broadstreet/Triton/Xandr being unused scaffolding) skip the router entirely: instantiate one GAMPlatform, pass to serve(), let multi-tenancy ride on Account.metadata['tenant_id']. For that shape the migration is "delete the registry, instantiate one GAMPlatform," not "translate registry into router." Adding a "Who needs this" callout at the top of §3.1 so single-adapter adopters don't waste time on the router pattern they don't need. Also fixes a verbatim duplicate paragraph at the end of §3.1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
May 4, 2026
Three more Step 0 investigations completed; doc updated.
(1) check_parameter_alignment.py analyzed
- Guard enumerates pairs of (mcp_wrapper, a2a_raw) from a hardcoded
tools list (lines 36-78). Does NOT enumerate "all callers of
_impl." Side-car's GAMDecisioningPlatform calling _impl directly
is invisible to the guard.
- Confirmed: zero allowlist additions needed.
(2) _already_approved sentinel works as-is
- compose_method (compose.py:173-194) passes req through unchanged
from before-hook to inner; no model_validate/model_copy/model_dump
on the request side.
- Dispatcher only model_dumps on response side (dispatch.py:1234,
1306-1307).
- Generated request models use extra='forbid' (validation-time only)
without frozen=True or validate_assignment=True; setattr lands in
__dict__ and persists through Python-level dispatch.
- No typed marker prototype needed for this experiment. Q4 design
question stays open as a Protocol RFC.
(3) Webhook signing parity does NOT hold (real finding)
- Salesagent's webhook_authenticator.py emits X-Webhook-Signature +
X-Webhook-Timestamp with payload f"{ts}.{json.dumps(payload,
separators=(',',':'), sort_keys=True)}" canonicalization.
- SDK's from_adcp_legacy_hmac emits X-AdCP-Signature +
X-AdCP-Timestamp + X-AdCP-Key-Id with different canonicalization.
- Different headers, different canonicalization, different scheme
entirely.
- §3.14's "adopters delete their webhook plumbing wholesale" claim
doesn't hold cleanly — production cutover requires buyer migration.
- Experiment validates SDK→SDK signing with adcp.WebhookReceiver as
test buyer. Does not validate the migration claim against actual
subscribed buyers.
Constraint added to Step 0:
- Local fork edits only. No upstream PRs to salesagent. The
scheduler skips become hardcoded constants or env-var consults
in our experiment fork; revert with `git checkout main`.
Updates flow through:
- Step 0.4: prereq is local fork patch, not alembic migration
- Step 0 Investigated section: three more ✅ items + one ⚠️
- Workstream 2.4: webhook test buyer is adcp.WebhookReceiver (not
a real subscribed buyer; that's out of scope post-experiment)
- Risks: webhook signing parity bullet rewritten with the correct
finding (incompatible, not "verify before run")
- Next steps adds 3a — correct §3.14 of #489 migration guide
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
examples/multi_platform_seller/MIGRATION_FROM_ADAPTER_REGISTRY.md— the translation guide for adopters running salesagent-shaped multi-tenant adapter-registry code.The headline message: business logic stays; cross-cutting concerns move into SDK primitives. Concrete before/after pairs with
salesagent/src/adapters/...file:line citations on every before-shape and shipping SDK primitives on every after-shape.Parent issue: #477 (the multi-platform proof). Implementation lands in parallel PR
bokelley/feat-platform-router. This is the doc-only PR; the directory will be co-populated with the worked example by the parallel PR. Merge order doesn't matter — if there's a path conflict at merge, rebase resolves cleanly.Sections
ADAPTER_REGISTRY→PlatformRouterAdServerAdapterABC →DecisioningPlatform+SalesPlatformcompose_method+ShortCircuitAccount.modeAccount.mode='mock'(the ~1,800 LOC deletion)comply_test_controllergateassert_media_buy_transitionUpstreamHttpClientAdcpError+UpstreamHttpClientprojectiondry_run, audit logger, tenant DB, admin UI)Reviewer
Brian O'Kelley (@bokelley) — salesagent author and the natural reviewer for whether the translation table reflects salesagent's actual shape.
Test plan
main🤖 Generated with Claude Code