Skip to content

feat(capabilities): self-report adcp.implementation.sdks (#1646) - #1812

Merged
bokelley merged 1 commit into
nextfrom
claude/issue-1646-implementation-sdks
Nov 2, 2026
Merged

bokelley merged 1 commit into
nextfrom
claude/issue-1646-implementation-sdks

Conversation

@bokelley

@bokelley bokelley commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Spec

AdCP 3.3 adds get_adcp_capabilities adcp.implementation.sdks[]: package name, exact resolved version, and the server-side components the SDK actually supplies. The components description is normative. List a component only if every responsibility that applies to it holds across all advertised task paths. Leave it out if coverage varies between paths. An empty list reports SDK use without claiming any component. A missing block means unknown.

Design

Automatic self-report. adcp.server.serve() (and adcp.decisioning.serve(), which delegates to it) installs adcp.server.implementation.ImplementationReporter, an SDK response enhancer. It runs on the shared create_tool_caller path for MCP and A2A, before the adopter's own response_enhancer, so that enhancer can still amend or remove the block. On a successful get_adcp_capabilities response it adds:

{"name": "adcp", "version": "<importlib.metadata.version('adcp')>", "components": [...]}

The version is the exact installed distribution version. If it can't be resolved, or it breaks the schema pattern (\S, at most 32 chars), nothing is emitted.

Component derivation (conservative):

Component Claimed when
transport always under serve()
types handler is a Decisioning PlatformHandler (a plain ADCPHandler may return raw dicts)
schema_validation request and response validation are strict, and the served versions exclude the 2.5 legacy adapter. Its translated responses are not re-validated, so the default version set never claims this.
authentication auth=BearerTokenAuth(...) on an HTTP transport, with no allow_unauthenticated and no mcp_discovery_tools / a2a_discovery_skills beyond DISCOVERY_TOOLS
idempotency the served capabilities declare adcp.idempotency.supported: true, _adcp_idempotency_external is not set, and every advertised task whose bundled request schema requires idempotency_key resolves to an IdempotencyStore.wrapped method (platform method for Decisioning). Tasks backed by AccountStore, such as sync_accounts, therefore block the claim.
webhooks Decisioning only: the SDK atomic task outbox owns delivery (task_webhook_owner_ready and not external), webhook_signing.supported, and no advertised task takes a webhook the SDK doesn't deliver (reporting_webhook, artifact_webhook, revocation_webhook, webhook_url, sync_agent_notification_configs). So a media-buy seller never claims it, because reporting webhooks are delivered by adopter code.
signing request_signature_verification is wired, request_signing.supported is declared, and outbound webhooks are either SDK-signed (webhooks claimed) or not offered at all
lifecycle never derived: domain state transitions are enforced by adopter calls to assert_*_transition, not by the framework

Override, opt-out and extension:

  • Any declared adcp.implementation is never overwritten. That covers Adcp(implementation=...) on DecisioningCapabilities (previously dropped by the projection, now emitted), the new capabilities_response(implementation=...), and a custom handler's own response.
  • serve(report_implementation=False) / ServeConfig(report_implementation=False) omits the block.
  • serve(report_implementation=ImplementationReport(extra_components=["lifecycle"], extra_sdks=[...])) adds components the SDK can't detect, or further SDK entries. An extra entry named adcp is refused.
  • adcp.server.adcp_sdk_entry(components) builds this SDK's entry for a hand-written block.
  • Implementation, Sdk and ImplementationComponent are exported from adcp.decisioning.capabilities (bundled models, so they fit Adcp).

Version projection. The block is added only when both the application version and the caller's wire version are at least 3.3-beta.0. A 3.0–3.2 caller, or a 3.2-pinned server, never sees it, which also avoids a recorded conversion loss on every 3.2 discovery call. Before 3.3, adcp.implementation was an open extension point. As a backstop, the existing 3.3 → 3.2 conversion already drops /adcp/implementation (field_unavailable).

Outside serve() (a direct handler.get_adcp_capabilities(), create_mcp_server, adcp.testing), nothing is emitted, since transport ownership can't be shown there; the spec reads that as unknown.

Breaking? It's additive: an optional field the 3.3 schema defines. Tests that compare a whole 3.3 capabilities response served through serve() will see the new key. MIGRATION_v9_to_v10.md says so and documents opt-out and override, so this is not marked !.

Tests

tests/test_implementation_report.py drives the reporter through create_tool_caller with strict validation, so negotiation, conversion and response-schema validation all run on the stamped response:

  • entry name and version come from distribution metadata; invalid or missing version emits nothing; the component order is pinned to the generated enum
  • plain-handler emission is schema-valid against GetAdcpCapabilitiesResponse
  • schema_validation is withheld for warn-only, partial or off validation and when 2.5 is served
  • authentication derivation: claimed for full coverage; withheld for allow_unauthenticated, a widened discovery set, or stdio
  • signing needs the verifier, the declaration, and no unsigned webhooks
  • Decisioning: types and idempotency claimed when every mutating task is wrapped; withheld for unwrapped or external; a declared block wins; no SDK outbox means no webhooks / signing; a direct handler call emits nothing
  • adopter-declared block preserved; extra_components / extra_sdks appended and validated
  • 3.1 / 3.2 callers and a 3.2-pinned server get no block
  • the adopter enhancer runs after the reporter and can amend it; a reporter failure still runs the adopter enhancer
  • serve() installs the reporter by default; report_implementation=False and ServeConfig opt-out and custom report

Docs: docs/adcp-3.3-sdk-inventory.md rows for adcp.implementation.sdks[] and adcp.implementation, plus a MIGRATION_v9_to_v10.md row.

Closes #1646

🤖 Generated with Claude Code

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — additive AdCP 3.3 implementation self-report (adcp.implementation.sdks[]), shipped under feat(capabilities):.

Checked:

  • Semver signal correct — all new surface (ImplementationReport, adcp_sdk_entry, report_implementation, Implementation/Sdk/ImplementationComponent) is purely additive; no public export removed/renamed, no required→optional flips, no enum members dropped. Non-breaking prefix is accurate.
  • Fail-closed component derivation — capabilities claimed only when provably agent-wide (idempotency gated on every mutating task being IdempotencyStore.wrapped + has_mutating at implementation.py:502; schema_validation withheld under 2.5 legacy adapter at :462; auth withheld on widened discovery sets at :598-609).
  • DR-0032/0033 pathway — handled as droppable, gated to 3.3+ on both application and wire version (:586-591), with a 3.0–3.2 regression test. Pathway + test present.
  • Import layering respected — domain-path imports only, no _generated.py reach-in; _COMPONENT_ORDER pinned to the generated enum by test.
  • No generated-code hand-edits, no credentials in ctx_metadata, CI gates intact.

Non-blocking note: _schema_shape swallows bundle-load errors (:369) — could theoretically over-claim webhooks on a missing bundle, but schemas ship bundled so this is practically unreachable; it only ever weakens an advisory, non-trust-boundary claim.

high_risk is true but all reasons are (modified) or (added) files with zero medium-or-higher findings — no escalation trigger. No findings, no gated paths, no team gate. Falls through to row 9.

@bokelley
bokelley force-pushed the claude/issue-1646-implementation-sdks branch from 5406418 to ec1f205 Compare November 2, 2026 03:50

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — AdCP 3.3 adcp.implementation.sdks[] self-report, additive and version-gated.

No blocking or medium findings. The reviewer confirmed the change is additive, opt-out, and fail-closed throughout:

  • Correct non-breaking semver signal (feat:, not feat!:), with a MIGRATION_v9_to_v10.md row for the new 3.3 response key.
  • _stamp gates on both application and wire version; 3.2 callers never see the block (covered by test_pre_3_3_callers_never_see_the_block).
  • 3.3→3.2 pathway is droppable on a non-mutating discovery response — compliant with the minor-release-pathway rule, and tested.
  • _facts only claims idempotency when mutating_covered and has_mutating; _schema_shape bare except only weakens claims; __call__ broad except is warn-only on advisory (non-durable/non-billing) state — acceptable.
  • Import layering clean (domain-path only, no _generated reach-in); forward-compat unions and ctx_metadata credentials untouched.
  • Tests cover every derivation branch and round-trip through create_tool_caller with strict validation.

Decision table: no critical/high (rows 1,6 n/a); gated_paths false and review_decision APPROVED (rows 2 n/a); high_risk is true but reasons are (added)/(modified) with zero medium findings (rows 3,4,5 n/a); no no-auto-approve team (row 7 n/a); fewer than three medium findings (row 8 n/a). Falls through to row 9 → approve. Prior decision was also approve; this is a clean follow-up.

Checked: semver signal, forward-compat unions, credential handling, version-translation pathway, public-export docs, test coverage.

AdCP 3.3 lets an agent report the SDKs it runs and the server-side
components each supplies. serve() (and adcp.decisioning.serve) now stamps
{"name": "adcp", "version": <installed>, "components": [...]} on
get_adcp_capabilities responses served at 3.3 or later, via an SDK
response enhancer that runs ahead of the adopter's own.

Components are claimed only when provably agent-wide: transport always;
types for Decisioning platforms; schema_validation for strict request and
response validation without the 2.5 legacy adapter; authentication for
BearerTokenAuth with no widened discovery set; idempotency when every
advertised mutating task is IdempotencyStore-wrapped; webhooks / signing
only when the SDK owns and signs every outbound webhook; lifecycle never.

A declared adcp.implementation always wins (DecisioningCapabilities.adcp
.implementation is now projected; capabilities_response gains
implementation=). serve(report_implementation=False) opts out;
ImplementationReport adds components or further sdks[] entries. 3.0-3.2
callers and 3.2-pinned servers never see the block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bokelley
bokelley force-pushed the claude/issue-1646-implementation-sdks branch from ec1f205 to 382302a Compare November 2, 2026 04:26

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — clean pass, no blocking or medium findings.

This is a subsequent review (prior decision: approve). The reviewer confirmed a clean pass over the one new load-bearing file, src/adcp/server/implementation.py, and the supporting changes.

What was checked:

  • Conservative capability derivation: _static_components gates schema_validation on strict request+response and excludes 2.5 legacy adapters; _sdk_owns_task_webhooks claims webhooks only when the SDK outbox owns delivery (verified against webhook_emit.py and PlatformHandler attrs); _stamp double-gates on application + wire version ≥ 3.3-beta.0 and never overwrites a declared adcp.implementation.
  • Semver signal correct: new exports and params are additive under feat(capabilities):, no ! and no breaking change to public surface.
  • DR-0032/0033 pathway: Droppable on a response field (3.0–3.2 callers never see it) with a test — pathway handling present.
  • Import layering clean: implementation.py imports only domain paths, no _generated or numbered classes; no hand-edits to domains/ or _generated.py.
  • No credentials in ctx_metadata; CI gates untouched.

Decision-table walk: no critical/high findings (row 1 n/a); gated_paths=false (row 2 n/a); high_risk reasons are only (modified)/(added), no (deleted) (row 3 n/a); no medium findings (rows 4, 5, 8 n/a); prior decision was approve, not escalate (row 6 n/a); no no-auto-approve team match (row 7 n/a). Falls through to row 9 → approve.

Note on high_risk: flag is true but driven by (modified)/(added) files with zero medium-or-higher findings — presumed safe per the reading-the-flag guidance. Not escalation-worthy.

@bokelley
bokelley merged commit f01d750 into next Nov 2, 2026
41 checks passed
@bokelley
bokelley deleted the claude/issue-1646-implementation-sdks branch November 2, 2026 05:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant