diff --git a/.changeset/storyboard-default-agent-3.0.x.md b/.changeset/storyboard-default-agent-3.0.x.md new file mode 100644 index 0000000000..08d9334b94 --- /dev/null +++ b/.changeset/storyboard-default-agent-3.0.x.md @@ -0,0 +1,25 @@ +--- +"adcontextprotocol": patch +--- + +spec(storyboard-schema): add optional storyboard-level `default_agent` field + +Closes #3894. Adds an optional top-level `default_agent: ` field to the storyboard authoring schema (`static/compliance/source/universal/storyboard-schema.yaml`). + +`default_agent` is the logical name (`sales`, `governance`, `creative`, etc.) the multi-agent runner falls back to when a step has no `step.agent` override and the tool has no unique specialism claimant in the runtime agents map. Resolved against the `agents` option passed to `runStoryboard({ agents: {…} })` — see adcp-client#1066 and adcp-client#1355. + +The runner already accepts `default_agent` via run-options. This change lets storyboard authors encode the topology intent in YAML once, rather than re-asserting `--default-agent sales` on every CI invocation. Cross-domain tools (`sync_creatives`, `list_creative_formats`, `comply_test_controller`) become deterministic without per-step `agent:` overrides. + +Strictly additive and backward-compatible: +- Single-agent runs ignore the field (precedent: `requires_scenarios`, `controller_seeding`). +- Existing 3.0.x storyboards keep working unchanged. +- Pre-existing run-options `default_agent` keeps the lower-precedence fallback slot. + +Resolution order (runner contract): +1. Step-level `agent:` override. +2. Unique specialism claimant in the runtime agents map. +3. Storyboard-level `default_agent` (this field). +4. Run-options `default_agent`. +5. Fail-fast (`unrouted_step`). + +Mirrors the `provides_state_for` precedent (#3775) for adding optional storyboard-schema fields on 3.0.x — small, additive authoring affordances that adopters need today and that don't bind 3.0 wire shape. diff --git a/static/compliance/source/universal/storyboard-schema.yaml b/static/compliance/source/universal/storyboard-schema.yaml index 5b7a0aacac..9329b017b4 100644 --- a/static/compliance/source/universal/storyboard-schema.yaml +++ b/static/compliance/source/universal/storyboard-schema.yaml @@ -53,6 +53,60 @@ # MUST exist in the source tree at build time. Duplicate IDs # across files are a build-time error.) # +# default_agent: string (optional — logical agent key used by multi-agent +# storyboard runners to route steps that do not have a unique specialism +# claimant in the runtime agents map. Resolved by the runner against the +# `agents` option passed to `runStoryboard({ agents: { sales: …, governance: …, … } })`. +# +# Key shape: free-form non-empty string, matched verbatim against the +# runtime `agents` map's keys. The spec does NOT constrain the key to the +# specialism enum (`sales`, `signals`, `governance`, `creative`, `brand`) +# because production multi-agent topologies legitimately fan out per-property +# (`nyt_sales`, `wsj_sales`), per-region (`sales_eu`, `sales_us`), or +# per-brand-rights-holder. Authors choose the convention that matches their +# operator's CI invocation; portability across operators is the author's +# concern, not the spec's. +# +# When to set it. Storyboards that exercise cross-domain tools — e.g., +# `sync_creatives`, `list_creative_formats` — do not name a single specialism +# the runner can match. The author has the most context for which logical +# tenant should receive these calls (usually "wherever the seller is": +# `sales`). Encoding `default_agent: sales` once in the YAML beats +# re-asserting it on every CI invocation. (Note: `comply_test_controller` +# is routed via `prerequisites.controller_seeding`, not this field — the +# controller is a back-channel, not a specialism claimant.) +# +# Resolution order (runner contract — see adcp-client#1066, adcp-client#1355): +# 1. Step-level `agent:` override (if declared on the step). +# 2. Specialism-claimant match against the runtime agents map (matched +# via each agent's `get_adcp_capabilities.supported_protocols`): +# - Exactly one agent claims the step's task's specialism → route there. +# - Zero claimants → fall through to slot 3. +# - Two or more claimants → runner MUST grade the step `unrouted_step` +# rather than picking arbitrarily. Multi-claim is an operator-config +# error; the storyboard cannot disambiguate it. Slot 3/4 do NOT +# rescue this case — silently picking one would mask the misconfig. +# 3. Storyboard-level `default_agent` (THIS field) resolved against the +# runtime agents map. When the field is set but the key is absent from +# the map, the runner MUST grade the step `default_agent_unresolved` +# and MUST NOT fall through to slot 4 — silent fallback would invisibly +# override the storyboard author's encoded intent. Slot 4 fires only +# when the storyboard does NOT declare this field. +# 4. Run-options `default_agent` passed to `runStoryboard({ default_agent })`. +# Same key-resolution rule as slot 3: set-but-unmatched grades +# `default_agent_unresolved`; unset falls through to slot 5. +# 5. Fail-fast — runner raises `unrouted_step` and grades the step failed. +# +# Single-agent runs ignore this field entirely — there is no map to resolve +# against, every step routes to the only configured agent. Authors SHOULD +# still declare it for clarity; multi-agent runners treat it as advisory and +# single-agent runners as a no-op. +# +# Validation. The `` MUST be a non-empty string. Validation that the +# key resolves to a configured tenant is a runtime concern (per the slot 3 +# rule above), not a schema concern — the same storyboard runs against +# different topologies.) +# # agent: # interaction_model: enum (stateless_transform | stateful_preloaded | stateful_push | stateless_generate | media_buy_seller | marketplace_catalog | owned_signals | si_platform | brand_rights_holder | governance_agent) # capabilities: string[] (AdCP capability flags: supports_transformation, has_creative_library, supports_generation, sells_media, accepts_briefs, supports_guaranteed, supports_non_guaranteed, catalog_signals)