Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/signals/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,26 @@ Each activation call specifies the destination platform and account:

The signal agent pushes segment membership to each platform. Sam gets back deployment IDs he can reference when building media buys.

### Buying through a sales agent

Sam also wants to run a sponsored article campaign through Wonderstruck, a premium publisher with its own sales agent. Instead of activating the signal on a specific DSP, Sam activates it on the sales agent directly — Wonderstruck handles its own DSP coordination:

```json
{
"$schema": "https://adcontextprotocol.org/schemas/v3/signals/activate-signal-request.json",
"signal_agent_segment_id": "shopgrid_new_to_brand",
"pricing_option_id": "po_shopgrid_retail_cpm",
"destinations": [
{
"type": "agent",
"agent_url": "https://wonderstruck.salesagents.example"
}
]
}
```

The sales agent records the activation internally. When Sam later calls `create_media_buy` through Wonderstruck, the signal-based targeting is already in place — Sam doesn't need to know which DSP Wonderstruck uses behind the scenes.

## Step 5: Build the campaign

<img src="/images/walkthrough/signals-05-campaign-targeting.png" alt="Sam views three targeting layers stacking on a large screen — location, audience, and purchase data overlap and glow where they intersect" style={{ width: '100%', borderRadius: '12px', marginBottom: '1rem' }} />
Expand Down
8 changes: 8 additions & 0 deletions docs/signals/specification.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ Signal activation is typically asynchronous:

Orchestrators MUST NOT assume immediate availability after activation request.

### Destination Type Selection

The `activate_signal` request supports two destination types. The choice depends on the buyer's execution path:

- Orchestrators buying through a Sales Agent SHOULD use `type: "agent"` destinations with the SA's URL. The SA handles downstream platform coordination — which DSP it uses is an implementation detail.
- Orchestrators buying directly on a DSP SHOULD use `type: "platform"` destinations. The orchestrator is responsible for ensuring the activation platform matches where campaigns will run.
- Signal agents MUST support both destination types per the destination schema.

## Schema Reference

| Schema | Description |
Expand Down
10 changes: 10 additions & 0 deletions docs/signals/tasks/activate_signal.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@ The activation key represents how to use the signal on a deployment target. It c
}
```

### Using Activation Keys

The activation key tells the buyer how to reference the signal on the destination. The execution path depends on the destination type:

**Platform destinations** — The `activation_key` contains a `segment_id`, a platform-native identifier. The signal agent pushed segment data to the DSP; the buyer references this key when configuring campaign targeting on that platform. The buyer is responsible for ensuring the activation platform matches where it will run campaigns.

**Agent destinations** — The `activation_key` confirms the signal is live on the sales agent. The SA records the activation internally and applies signal-based targeting when fulfilling media buys through `create_media_buy`. The buyer does not need to know which DSP the SA uses — downstream platform coordination is the SA's responsibility.

**Choosing a destination type**: Use `type: "platform"` when buying directly on a DSP. Use `type: "agent"` when buying through a Sales Agent — the SA coordinates its own DSP targeting as an implementation detail.

## Protocol-Specific Examples

The AdCP payload is identical across protocols. Only the request/response wrapper differs.
Expand Down
6 changes: 3 additions & 3 deletions docs/storyboards/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
# id: string (unique identifier, e.g., "creative_template")
# version: string (semver, e.g., "1.0.0")
# title: string (human-readable title)
# category: enum (creative_template | creative_ad_server | creative_sales_agent | creative_generative | media_buy_seller | media_buy_guaranteed_approval | media_buy_non_guaranteed | media_buy_proposal_mode | media_buy_governance_escalation | media_buy_catalog_creative)
# category: enum (creative_template | creative_ad_server | creative_sales_agent | creative_generative | media_buy_seller | media_buy_guaranteed_approval | media_buy_non_guaranteed | media_buy_proposal_mode | media_buy_governance_escalation | media_buy_catalog_creative | signal_marketplace | signal_owned)
# summary: string (one-line description for listings)
# narrative: string (paragraph explaining the overall flow)
#
# agent:
# interaction_model: enum (stateless_transform | stateful_preloaded | stateful_push | stateless_generate | media_buy_seller)
# capabilities: string[] (AdCP capability flags: supports_transformation, has_creative_library, supports_generation, sells_media, accepts_briefs, supports_guaranteed, supports_non_guaranteed)
# interaction_model: enum (stateless_transform | stateful_preloaded | stateful_push | stateless_generate | media_buy_seller | marketplace_catalog | owned_signals)
# capabilities: string[] (AdCP capability flags: supports_transformation, has_creative_library, supports_generation, sells_media, accepts_briefs, supports_guaranteed, supports_non_guaranteed, catalog_signals)
# examples: string[] (real-world examples: "Celtra", "Innovid")
#
# caller:
Expand Down
276 changes: 276 additions & 0 deletions docs/storyboards/signal_marketplace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
id: signal_marketplace
version: "1.0.0"
title: "Marketplace signal agent"
category: signal_marketplace
summary: "Signal agent that resells third-party data provider signals with verifiable catalog provenance."

narrative: |
You operate a signal marketplace — an intermediary that aggregates audience data from
multiple third-party providers and makes it available to buyers through a single interface.
Think LiveRamp Data Marketplace, Oracle Data Cloud, or Lotame.

Your agent searches across catalogs published by data providers in their adagents.json
files. Buyers discover signals through natural language queries, verify provenance by
checking the data provider's catalog directly, and activate signals on DSPs or sales
agents for campaign targeting.

The key property of marketplace signals: provenance is independently verifiable. Each
signal traces back to a data_provider_domain whose adagents.json lists your agent as
authorized. Buyers can (and should) verify this before spending.

This storyboard walks through discovery, verification, and both activation patterns —
activating directly on a DSP (buyer manages targeting) and activating on a sales agent
(SA handles downstream coordination).

agent:
interaction_model: marketplace_catalog
capabilities:
- catalog_signals
examples:
- "LiveRamp Data Marketplace"
- "Oracle Data Cloud"
- "Lotame"

caller:
role: buyer_agent
example: "Pinnacle Agency (buyer)"

prerequisites:
description: |
The buyer has a campaign brief with targeting objectives. The test kit provides
sample signal definitions, pricing options, and destination configurations that
match the training agent's signal providers.
test_kit: "test-kits/nova-motors.yaml"

phases:
- id: discovery
title: "Signal discovery"
narrative: |
The buyer describes what they need in natural language. Your agent searches
across all authorized data provider catalogs and returns matching signals with
pricing, coverage estimates, and value types.

This is where marketplace agents earn their keep — the buyer doesn't need to
know which providers exist or what taxonomies they use. One query, many sources.

steps:
- id: search_by_spec
title: "Discover signals from a campaign brief"
narrative: |
The buyer's platform translates a campaign brief into a get_signals call.
Your agent searches catalogs from every authorized data provider and returns
what matches — automotive intent from one provider, geo data from another,
retail purchase history from a third.
task: get_signals
schema_ref: "signals/get-signals-request.json"
response_schema_ref: "signals/get-signals-response.json"
doc_ref: "/signals/tasks/get_signals"
stateful: false
expected: |
Return matching signals from multiple data providers. Each signal must include:
- signal_agent_segment_id for activation
- signal_id with source, data_provider_domain, and id
- name, description, and value_type (binary, categorical, or numeric)
- coverage_percentage (audience reach estimate)
- pricing_options with at least one pricing model
- signal_type: "marketplace"

sample_request:
signal_spec: "In-market EV buyers with high purchase propensity, near auto dealerships"

validations:
- check: response_schema
description: "Response matches get-signals-response.json schema"
- check: field_present
path: "signals[0].signal_agent_segment_id"
description: "Each signal has a signal_agent_segment_id"
- check: field_present
path: "signals[0].signal_id.data_provider_domain"
description: "Each signal traces to a data provider domain"
- check: field_present
path: "signals[0].pricing_options"
description: "Each signal has pricing options"
- check: field_present
path: "signals[0].coverage_percentage"
description: "Each signal has a coverage estimate"

- id: search_by_ids
title: "Look up specific signals by ID"
narrative: |
The buyer already knows which signals they want — maybe from a previous
campaign or a provider's documentation. They pass signal_ids directly
instead of a natural language query.
task: get_signals
schema_ref: "signals/get-signals-request.json"
response_schema_ref: "signals/get-signals-response.json"
doc_ref: "/signals/tasks/get_signals"
stateful: false
expected: |
Return the exact signals requested, with full metadata and pricing.
If a signal_id doesn't exist, omit it from results — don't error.

sample_request:
signal_ids:
- source: "catalog"
data_provider_domain: "tridentauto.example"
id: "likely_ev_buyers"
- source: "catalog"
data_provider_domain: "meridiangeo.example"
id: "competitor_visitors"

validations:
- check: response_schema
description: "Response matches schema"
- check: field_present
path: "signals"
description: "Response contains a signals array"

- id: verification
title: "Catalog verification"
narrative: |
Before activating third-party data, the buyer verifies provenance. They fetch
the data provider's adagents.json directly and confirm the signal exists and
your agent is authorized. This independent check is outside the AdCP protocol —
but your agent must return the metadata that makes it possible.

This phase tests that your get_signals responses include verifiable provenance
data: signal_id.source is "catalog" and signal_id.data_provider_domain points
to a real domain whose adagents.json the buyer can fetch independently.

steps:
- id: verify_provenance_metadata
title: "Confirm signals carry verifiable provenance"
narrative: |
The buyer looks up a specific signal by ID and checks that the response
includes the metadata needed for independent verification — source is
"catalog" and data_provider_domain points to a fetchable adagents.json.
The actual HTTP fetch of adagents.json is the buyer's responsibility, but
your agent must provide the domain to fetch from.
task: get_signals
schema_ref: "signals/get-signals-request.json"
response_schema_ref: "signals/get-signals-response.json"
doc_ref: "/signals/data-providers"
stateful: false
expected: |
Return the requested signal with verifiable provenance metadata:
- signal_id.source is "catalog"
- signal_id.data_provider_domain matches a real domain
The buyer will independently fetch that domain's adagents.json to confirm
your agent is listed in authorized_agents.

sample_request:
signal_ids:
- source: "catalog"
data_provider_domain: "shopgrid.example"
id: "new_to_brand"

validations:
- check: field_value
path: "signals[0].signal_id.source"
description: "Signal source is 'catalog' (verifiable via adagents.json)"
- check: field_present
path: "signals[0].signal_id.data_provider_domain"
description: "Data provider domain is present for independent verification"

- id: platform_activation
title: "Activate on a DSP"
narrative: |
The buyer activates a signal directly on a DSP platform. The signal agent pushes
segment data to the platform, and the buyer gets back a segment_id they can
reference when configuring campaign targeting.

Use platform destinations when the buyer is managing DSP campaigns directly —
not through a sales agent.

steps:
- id: activate_on_platform
title: "Activate signal on a DSP"
narrative: |
The buyer selects a signal and a DSP destination. The signal agent pushes
the segment to the platform. This is typically asynchronous — the initial
response shows is_live: false with an estimated duration, and the buyer
polls for completion.
task: activate_signal
schema_ref: "signals/activate-signal-request.json"
response_schema_ref: "signals/activate-signal-response.json"
doc_ref: "/signals/tasks/activate_signal"
stateful: true
expected: |
Return a deployment with:
- type: "platform" matching the requested destination
- is_live: false initially (async activation)
- estimated_activation_duration_minutes
After polling, the deployment should show:
- is_live: true
- activation_key with type: "segment_id" and a platform-native segment ID
- deployed_at timestamp

sample_request:
signal_agent_segment_id: "trident_likely_ev_buyers"
pricing_option_id: "po_trident_ev_cpm"
destinations:
- type: "platform"
platform: "the-trade-desk"
account: "agency-123-ttd"

validations:
- check: response_schema
description: "Response matches activate-signal-response.json schema"
- check: field_present
path: "deployments[0].type"
description: "Deployment includes type"
- check: field_value
path: "deployments[0].type"
description: "Deployment type is 'platform'"

- id: agent_activation
title: "Activate on a sales agent"
narrative: |
The buyer activates a signal on a sales agent instead of a DSP. This is the
right pattern when the buyer is purchasing media through the SA — the SA handles
its own DSP coordination.

The buyer doesn't need to know which DSP the SA uses. The activation key confirms
the signal is live on the SA, and the SA applies targeting when fulfilling media
buys through create_media_buy.

steps:
- id: activate_on_agent
title: "Activate signal on a sales agent"
narrative: |
The buyer activates a signal with the sales agent's URL as the destination.
Agent activations are typically synchronous — the SA records the activation
immediately and returns a key_value activation key.
task: activate_signal
schema_ref: "signals/activate-signal-request.json"
response_schema_ref: "signals/activate-signal-response.json"
doc_ref: "/signals/tasks/activate_signal"
stateful: true
expected: |
Return a deployment with:
- type: "agent" matching the requested destination
- agent_url matching the SA's URL
- is_live: true (sync activation)
- activation_key with type: "key_value"
- deployed_at timestamp

The SA records the activation internally. When the buyer later calls
create_media_buy through this SA, signal-based targeting is already
in place.

sample_request:
signal_agent_segment_id: "shopgrid_new_to_brand"
pricing_option_id: "po_shopgrid_retail_cpm"
destinations:
- type: "agent"
agent_url: "https://wonderstruck.salesagents.example"

validations:
- check: response_schema
description: "Response matches activate-signal-response.json schema"
- check: field_present
path: "deployments[0].activation_key"
description: "Deployment includes activation key"
- check: field_value
path: "deployments[0].type"
description: "Deployment type is 'agent'"
Loading
Loading