Skip to content

RFC: The agent connection layer — one place to configure a buyer–seller relationship #7015

Description

@bokelley

Problem

Configuration between one authenticated caller and one seller agent is scattered across three layers, and only one of them is unified:

Concern Where it lives today Cost
Caller-anchored webhooks (capabilities.changed) sync_agent_configuration (#6966) one call ✅
Account-anchored webhooks (creative.*, account.*, product.*, signal.*, …) each account's notification_configs[] via sync_accounts N calls to sign up, N calls to unwind a bad endpoint
Media-buy delivery reports each buy's push_notification_config M configs
Buyer version/capability declaration nowhere durable webhooks have no request context, so async payload versioning has no negotiation channel at all
Push when seller-side connection state changes (destination validatingready, expiring share grants) nowhere buyers poll get_agent_configuration

Concretely: an agency principal managing 200 accounts at one seller makes 201 registration calls to "sign me up for everything," and when the endpoint is misconfigured, 200 more to pull it. A seller that wants to fire a webhook at a buyer six weeks after registration has no way to know what payload version the buyer speaks. And the moment a Delta share grant is about to expire, nobody gets told.

Proposal

Build on #6966's principal, proof, versioning, and revocation machinery. Three additions, one rule flip:

1. Connection-level webhook subscriptions

Allow caller-level subscribers (the notification_configs section) to include account-anchored event types, with authorization evaluated at fire time: each fire covers only accounts the caller is currently authorized for. Losing account authority silently stops that account's fires — the subscription is standing; the authorization is not.

  • This flips the current enum rule (notification-type.json today rejects caller-level entries carrying account-anchored types), which is why this RFC should land while protocol.agent_configuration is still experimental.
  • No wildcard. "Everything" is an enumerated event_types list plus an explicit include_future_event_types: true opt-in for callers who want new invalidation-style events to flow automatically.
  • Account-level and connection-level subscriptions are independent; both fire, receivers dedupe by notification_id (every account-anchored event already carries stable IDs).
  • Subscriber-level active: false is a one-call kill switch: all fires to that endpoint across all accounts halt within the seller's advertised interval — the webhook twin of feat(protocol): add agent configuration sync #6966's destination suspension.
  • Once feat(reporting): add managed reporting status schemas #6953 lands, reporting.delivery_ready registers here too, matching the connection-scoped reporting destinations it fires about.
  • Deferred: media-buy-anchored delivery reports (scheduled, final, …) keep per-buy registration; they carry per-buy cadence choices. A connection-level default with per-buy override is a later step.

2. A declarations section — the flip side of capability negotiation

A third replaceable section in sync_agent_configuration where the buyer declares consumption facts: supported AdCP versions for async payloads, webhook signing algorithms it can verify, experimental features it opts into, accepted transports. The seller returns the accepted intersection in readback state.

  • Solves async payload versioning: the durable declaration is the version-negotiation channel that webhook fires lack.
  • Discipline: bounded, enumerable declarations of fact about what the buyer can consume — not an open-ended preference language, and not a second capability document. get_adcp_capabilities stays objective seller facts; this stays caller-declared facts; the intersection is the contract.

3. A connection.changed caller-anchored event

An invalidation doorbell for seller-driven connection-state transitions: destination validatingready, setup.expires_at approaching, proof invalidated, accepted declaration intersection changed. Payload is invalidation-only; receivers repair by reading get_agent_configuration. This closes the poll loop for destination setup and gives buyers early warning before delivery breaks, following the exact capabilities.changed pattern.

Non-goals

  • No role field. A campaign governance agent and a buyer agent are different transport principals with their own connections; what each may bind is an account-authorization fact, not a self-asserted body field. Same task, same schema, role-blind connection layer.
  • No cross-principal visibility or linking (unchanged from feat(protocol): add agent configuration sync #6966).
  • No third capability document.

Relationship to in-flight work

Questions for the WG

  1. Is the problem real for you? Especially multi-account operators: how many per-account webhook registrations do you carry today, and have you had to unwind a bad endpoint account-by-account?
  2. Is fire-time authorization scoping the right model, or do connection-level subscriptions need explicit per-account filters in v1? (Proposal: all-authorized-accounts in v1, filters later if pulled.)
  3. When: this flips a rule feat(protocol): add agent configuration sync #6966 currently ships. Land it while the surface is experimental, or let feat(protocol): add agent configuration sync #6966 stabilize a cycle first?

An implementation PR (stacked on #6966) will follow this issue for concreteness; schemas remain subject to this discussion.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.rfcProtocol change — auto-adds to roadmap board

    Type

    No type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions