> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Lab: reconcile a shared account

> Connect to an existing seller account, observe changes initiated elsewhere, drain the durable account feed, and repair authoritative state.

# 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:

1. subscribe to `account.change_recorded`;
2. acquire a latest checkpoint before snapshotting;
3. enumerate authoritative state;
4. let the training seller simulate external creation and modification;
5. drain `list_account_changes`; and
6. locally allowlist `repair.task`, construct a validated request from the
   authenticated account and resource identity, and reread the resource.

<Warning>
  This lab exercises the AdCP 3.2 draft in [RFC #6810](https://github.com/adcontextprotocol/adcp/issues/6810). The surface can change before ratification.
</Warning>

<Warning>
  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.
</Warning>

## 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](https://agenticadvertising.org/dashboard).
Durable feed state and cursors are principal-scoped, so a shared public key is
not suitable for this exercise.

```bash theme={null}
export AGENT_URL="https://test-agent.adcontextprotocol.org/sales/mcp"
export ADCP_AUTH_TOKEN="<your-api-key>"
```

Use this existing shared sandbox account throughout:

```json theme={null}
{
  "account_id": "acc_luma_shared"
}
```

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:

```json theme={null}
{
  "account": {
    "change_feed": {
      "supported": true,
      "read_task": "list_account_changes",
      "registration_task": "sync_accounts",
      "event_type": "account.change_recorded"
    }
  }
}
```

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:

```json theme={null}
{
  "account": {
    "account_id": "acc_luma_shared"
  },
  "starting_position": "latest",
  "max_results": 100
}
```

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:

1. Which surface is authoritative current state?
2. Why is `webhook_activity[]` not the account change feed?
3. Why must the cursor exist on an empty response?
4. What does `has_more: false` mean when a connected source is unavailable?
5. 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.

## Related reading

* [`list_account_changes`](/dist/docs/3.2.0-beta.9/accounts/tasks/list_account_changes)
* [Snapshot and log](/dist/docs/3.2.0-beta.9/protocol/snapshot-and-log)
* [`sync_accounts` account subscriptions](/dist/docs/3.2.0-beta.9/accounts/tasks/sync_accounts#account-change-feed-notifications)
* [`list_creatives`](/dist/docs/3.2.0-beta.9/creative/task-reference/list_creatives)
