Problem
GetSignalsRequest.deliver_to serves two unrelated purposes in a single required field:
- Signal discovery — "What audiences can I target on your platform?" (caller is talking to the sales agent directly)
- Deployment routing — "What audiences can you push to these DSPs/agents?" (caller is talking to a data provider)
These are fundamentally different questions. When calling get_signals on a platform's sales agent (Snap, LinkedIn, etc.), the caller already knows where the signals will be used — on that platform. Requiring deliver_to forces every caller to construct a Destination object pointing back at the agent they're already talking to, which is meaningless.
Impact on LLM callers
The Destination type is a discriminated union (type: 'platform' | 'agent') intersected with Record<string, unknown>. This generates deeply nested JSON Schema that LLMs struggle to parse:
{
"anyOf": [
{ "allOf": [
{ "type": "object", "additionalProperties": { "anyOf": [{}, {"not": {}}] } },
{ "type": "object", "properties": { "type": {"const": "platform"}, "platform": {"type": "string"} } }
]},
{ "allOf": [
{ "type": "object", "additionalProperties": { "anyOf": [{}, {"not": {}}] } },
{ "type": "object", "properties": { "type": {"const": "agent"}, "agent_url": {"type": "string"} } }
]}
]
}
In practice, LLM callers fail on the first attempt and need multiple retries to figure out the correct format (observed in real usage with Claude calling LinkedIn AdCP).
Current workaround
Sales agent implementations either:
- Make
deliver_to optional and ignore it entirely
- Auto-fill it with their own agent URL before passing to the handler
Both approaches indicate the field doesn't belong as a required parameter for the platform-native signal discovery case.
Proposal
Separate the two concerns. One option:
interface GetSignalsRequest {
signal_spec?: string
signal_ids?: SignalID[]
// Optional: filter signals to those activatable on specific agents/platforms
// When omitted, returns all signals available on the current agent
target_agents?: Destination[]
// Geo filter (still useful for both cases)
countries?: string[]
filters?: SignalFilters
max_results?: number
// ...
}
- Calling
get_signals on a sales agent with no target_agents → returns platform-native signals
- Calling
get_signals on a data provider with target_agents: [{type: 'agent', agent_url: '...'}] → returns signals activatable on those agents
countries stays as a standalone filter since it's useful in both cases
This also fixes the LLM ergonomics issue since target_agents becomes optional and only needed for the data marketplace case.
Secondary issue: Record intersection pattern
The Record<string, unknown | undefined> & { ... } pattern used for extensibility across all AdCP types produces poor JSON Schema output. Consider using additionalProperties: true on the object directly instead — same semantics, much cleaner serialization for LLM tool schemas.
Problem
GetSignalsRequest.deliver_toserves two unrelated purposes in a single required field:These are fundamentally different questions. When calling
get_signalson a platform's sales agent (Snap, LinkedIn, etc.), the caller already knows where the signals will be used — on that platform. Requiringdeliver_toforces every caller to construct aDestinationobject pointing back at the agent they're already talking to, which is meaningless.Impact on LLM callers
The
Destinationtype is a discriminated union (type: 'platform' | 'agent') intersected withRecord<string, unknown>. This generates deeply nested JSON Schema that LLMs struggle to parse:{ "anyOf": [ { "allOf": [ { "type": "object", "additionalProperties": { "anyOf": [{}, {"not": {}}] } }, { "type": "object", "properties": { "type": {"const": "platform"}, "platform": {"type": "string"} } } ]}, { "allOf": [ { "type": "object", "additionalProperties": { "anyOf": [{}, {"not": {}}] } }, { "type": "object", "properties": { "type": {"const": "agent"}, "agent_url": {"type": "string"} } } ]} ] }In practice, LLM callers fail on the first attempt and need multiple retries to figure out the correct format (observed in real usage with Claude calling LinkedIn AdCP).
Current workaround
Sales agent implementations either:
deliver_tooptional and ignore it entirelyBoth approaches indicate the field doesn't belong as a required parameter for the platform-native signal discovery case.
Proposal
Separate the two concerns. One option:
get_signalson a sales agent with notarget_agents→ returns platform-native signalsget_signalson a data provider withtarget_agents: [{type: 'agent', agent_url: '...'}]→ returns signals activatable on those agentscountriesstays as a standalone filter since it's useful in both casesThis also fixes the LLM ergonomics issue since
target_agentsbecomes optional and only needed for the data marketplace case.Secondary issue: Record intersection pattern
The
Record<string, unknown | undefined> & { ... }pattern used for extensibility across all AdCP types produces poor JSON Schema output. Consider usingadditionalProperties: trueon the object directly instead — same semantics, much cleaner serialization for LLM tool schemas.