Reconcile a shared seller account
Real advertiser accounts are not exclusively managed by one buyer agent. A
seller operator, another authorized buyer, automation, or a connected platform
can change the same campaigns and creatives while your agent is idle.
This lab teaches the buyer convergence loop:
- subscribe to
account.change_recorded;
- acquire a latest checkpoint before snapshotting;
- enumerate authoritative state;
- let the training seller simulate external creation and modification;
- drain
list_account_changes; and
- locally allowlist
repair.task, construct a validated request from the
authenticated account and resource identity, and reread the resource.
This lab exercises the AdCP 3.2 draft in RFC #6810. The surface can change before ratification.
The hosted training seller does not advertise this capability until its
durable server-side feed store is enabled. The reference scenario remains
available to local and compliance runners; a production deployment must not
claim the 90-day retention guarantee with process-local state.
Learning objectives
By the end, you can:
- explain why task responses are insufficient on a shared account;
- distinguish current snapshots, durable business changes, notification
wake-ups, and
webhook_activity[] transport diagnostics;
- close the multi-read bootstrap race with a
latest checkpoint;
- process creation and modification your agent did not initiate; and
- recover from a missed webhook or
CURSOR_EXPIRED without accepting stale
state.
Connect to the training seller
After the hosted seller advertises account.change_feed, use a caller-unique API key from the
AgenticAdvertising.org dashboard.
Durable feed state and cursors are principal-scoped, so a shared public key is
not suitable for this exercise.
Use this existing shared sandbox account throughout:
The account is deliberately not buyer-exclusive. The training seller models a
generic connected platform that can modify its AdCP-visible resources.
Exercise
1. Confirm capability and register the wake-up
Call get_adcp_capabilities and require:
Use sync_accounts settings-update mode with
accounts[0].account.account_id: "acc_luma_shared" and
accounts[0].notification_configs[] to register an HTTPS endpoint for
account.change_recorded. The seller proves control before the subscription
becomes active. Persist the returned subscriber configuration.
2. Acquire C0 and snapshot
Before reading any account resources, call:
Persist the returned cursor as C0 even when changes is empty. Then enumerate
list_accounts, get_media_buys, and list_creatives, including every status
and page. A default active-only media-buy read is not a complete baseline.
3. Simulate activity outside your agent
Call comply_test_controller with scenario: "seed_creative", a new,
run-unique creative_id, and a complete image creative fixture. This
represents the connected platform adding a creative while your buyer agent is
idle; do not call sync_creatives first. A run-unique ID keeps repeated lab
runs independent while preserving the controller’s seed idempotency contract.
The training seller first commits the new creative and account change
record, then emits account.change_recorded to your active endpoint. The
webhook’s notification_id equals its change_id.
4. Drain and repair
Call list_account_changes with C0. Inspect:
resource.type and resource.resource_id;
origin.kind: "connected_platform";
changed_paths;
repair.task: "list_creatives"; and
- the new returned cursor.
Now call list_creatives for the resource ID and accept that response as
current truth. Do not reconstruct the creative from webhook or change-record
metadata, and never execute a feed-supplied task name or arguments directly.
An unknown repair hint requires a safe full account rescan.
5. Observe an external modification
Using the same creative ID, call comply_test_controller with
scenario: "force_creative_status", status: "rejected", and a categorical
rejection reason. This represents connected-platform policy review after the
creative was added.
Verify that a second account.change_recorded wake-up arrives even though the
buyer called no creative mutation task. Drain from the cursor returned in step
4 and require a status_changed record with
origin.kind: "connected_platform". Reread list_creatives and confirm that
the authoritative status is now rejected.
6. Prove tail and recovery behavior
Call list_account_changes again with the new cursor. A caught-up response has
changes: [], has_more: false, and still returns a cursor. Persist it.
Then explain both recovery cases:
- Webhook missed: poll from the persisted cursor; the durable feed closes
the gap.
CURSOR_EXPIRED: acquire a new latest checkpoint, rebuild every
authoritative snapshot, then drain after that checkpoint. Never silently
restart from the oldest retained record and pretend the gap is complete.
Assessment
You pass when you can demonstrate externally initiated creation and
modification end to end and correctly answer:
- Which surface is authoritative current state?
- Why is
webhook_activity[] not the account change feed?
- Why must the cursor exist on an empty response?
- What does
has_more: false mean when a connected source is unavailable?
- Which data must never appear in a change record?
The expected answers are: the locally allowlisted repair read; transport attempts are not
business changes; the empty cursor is the resumable tail checkpoint; caught up
to seller ingestion is not necessarily caught up to an unavailable upstream;
and credentials, financial account details, raw audience members/events, and
unbounded resource payloads stay out of the feed.