Agents make the deal: what a seller agent does in OpenProposal

On 3 November, at IAB Tech Lab's Agentic Bootcamp in London, Bedrock Platform CTO Damian Naglak ran a live campaign on stage. Bedrock's buyer agent read a brief, compared offers from two publishers, asked one of them for more, and agreed three private auction deals in a few minutes. Bedrock's bidder then took over and decided on each impression.
The two publishers were seller agents, and we built them. This post is the seller's side of that demo: what OpenProposal is, what a seller agent has to do with it, and what we learned building two of them.
OpenProposal is a shelf a buyer agent can read
A buyer agent may screen fifty sellers for one brief. It cannot read fifty rate cards, PDFs and email threads. OpenProposal gives every seller the same object to describe what it sells, so the agent can compare offers at machine speed and carry one of them all the way to binding terms.
The object has two levels:
- Proposal: the pitch. Who is selling, how long the offer holds, a
descriptionandbest_forwritten for a model to read, and the negotiation history. - Line items: the things you can actually buy, each in exactly one channel (web display, CTV, audio). Every line item is self-contained: it inherits nothing from the proposal or from its siblings, so a buyer can take one line item and hand it to a downstream system as it is.

A proposal is either standard (the seller publishes it for anyone) or custom (built in answer to a buyer's brief). Each line item says which execution route it supports in transaction_mechanism: direct IO, programmatic guaranteed, preferred deal, private auction or open auction. OpenProposal does not replace the execution standards; a deal still lands in OpenDirect, the Deals API or OpenRTB.
Every field says who may change it
This is the idea that makes OpenProposal work for agents, and the one a seller should understand first. Every field carries a marker that says who can change it and how:
| Marker | Who changes it | Example |
|---|---|---|
seller-set | Only the seller | gross_rate, description |
selectable | The buyer picks from a menu the seller declared, each option with its price | audiences, exclusivity, deal type |
settable | The buyer sets a value inside the seller's bounds | the commitment: budget and flight dates |
requestable | The buyer may ask for more than offered; the seller must agree | committed_metrics, such as viewability |
derived | Nobody; it is computed | version, agreed_rate, status |
The consequence is big. Because the seller publishes the menu and the price of every option up front, the buyer agent can compose a deal without asking the seller anything: pick the audience, add exclusivity, set the budget, and the price follows from the offer. The spec calls this constraint satisfaction rather than negotiation. Only a requestable ask, something outside the menu, needs the seller to answer.
For a publisher this moves the work. You no longer answer every request; you decide once what is on the menu, what each option costs, and how far you will go when someone asks for more.
From brief to deal in five calls
The spec defines the object, not the API around it. Our seller agents expose it as five tools over MCP, the protocol AI apps use to call tools:

get_proposals: the buyer sends its brief and gets a summary of every offer, enough to screen many sellers quickly.get_proposal: the full offer, every line item with all its options and prices.request_change: the buyer asks for something outside the menu. The seller answersaccepted,counteredordeclined. A counter comes back as version 2 of the offer, with the new price and an entry innegotiation_history.submit_commitment: the buyer commits budget and flight dates to one line item. The seller checks everything, prices it and returnsagreedwith adeal_id.get_proposal_status: the final state, for the buyer's records.
We never told Bedrock's agent this order. Each tool description says what it returns and what to call next, and the agent found the path from the brief alone. In a protocol where the client is a model, tool descriptions are product copy: they decide whether the flow works without hints.
What happened on stage
The brief was for Overtake, a fictional UK car brand launching a flying car: 300,000 USD over 10 to 24 November, split across connected TV, news web and a car-enthusiast site. On the car site it asked for an affluent tech audience and no other car brands in launch week. On news web it asked for at least 70% viewability.
Two seller agents answered, both running on one codebase and differing only in configuration:
- Fastlane, a UK car magazine: web display with priced audiences, launch-week exclusivity as a selectable option, and a list of buyer agents it is willing to talk to.
- The Northlight, a national news site: web display with a 60% viewability ceiling, and connected TV capped per household.
The buyer picked the audience and exclusivity from Fastlane's menu without a round trip. It asked The Northlight for 70% viewability, above the declared ceiling, so that was a requestable ask: The Northlight countered with version 2 at a higher CPM, and the buyer accepted. Then it committed to three line items and got three deal ids.



On stage, an agreed commitment also switched the Overtake ads on in each publisher's page, so the audience could see the deal land.
The seller keeps control of its price
Selling through an agent raises an obvious worry: will a model talk my price down? We built the sellers so it cannot.

- The floor never leaves the server. The spec offers an optional
pricing[].floor, the lowest rate the seller will consider, so a buyer could check a counter without a round trip. We chose not to send it: it tells the buyer exactly how far down to push. Our floor lives in the seller's configuration, and our tests check that it appears in no response, error messages included. - The buyer never sends a price.
request_changeandsubmit_commitmenttake a selection, a budget, dates and the change asked for. The seller always works the price out itself, from the offer it published. Asking for a lower price is not something the API can express. - The seller decides how far it bends. Each seller has rules for what it will raise, how far, and for what extra CPM. The Northlight raises viewability up to 70%; Fastlane declines every change. Both are valid answers in OpenProposal.
- Same question, same answer. No randomness and no clock in the pricing logic. The same commitment always gives the same
deal_id, so a buyer that retries with the same idempotency key gets the original deal, not a second one. - The seller chooses who it talks to. Fastlane only answers buyer agents on its list. The spec models this with
eligible_biddersandrequired_credentials; for the demo a header was enough.
Pricing and validation sit in one module that both request_change and submit_commitment call, so a counter offer and the final commitment can never disagree on price.
Agents make the deal, bidders buy the impressions
The buyer chose PRIVATE_AUCTION for all three deals, and that choice is the point of the demo. An agent is good at the slow part: reading offers, comparing them with a brief, asking for more and agreeing terms. It is the wrong tool for deciding on each impression in milliseconds. A bidder is built for that.

— Damian Naglak, CTO, Bedrock PlatformA deal is agreed once, but a campaign keeps moving. The list of people who've already seen your ad grows every minute. Deciding per impression lets buyers follow that, shift budget or raise a bid, without going back to the negotiating table.
In a private auction, the agreed_rate from the seller agent is the floor the buyer bids from, not a fixed price. The spec maps a private auction to the Deals API and an OpenRTB 2.6 deal, so the deal_id the seller agent mints is what the SSP and the bidder use from there on.
Where the draft was silent, we decided
A draft leaves gaps, and a working seller has to fill each one. A few we hit:
- There is no
deal_idfield. The spec says a deal id is issued at commitment time but not where it goes. We return it at the top level of the commitment result and derive it from a hash of the commitment, which makes it idempotent without a database. - The list of line items has no key name. The spec draws
Proposal → Line Items[]; we called itline_items. - Selectable fields need a price for every option. We read the spec as: every option outside
included[]must carry a price delta, otherwise the buyer cannot compose without a round trip. Our schema rejects offers that break this. - Some terms have no shape yet. Exclusivity is selectable, but the spec shows no exclusivity term; we used
{ id, name }.
Some of these are open questions in the spec itself, such as whether per-field mutability stays in 3.0 at all. We will cover them, and what a production seller agent needs beyond a demo, in the next post.
Building a seller agent?
We build seller agents for publishers on AAMP and AdCP: an agent-readable offer, pricing rules that hold under pressure, and the path from an agreed deal to your ad server. If you want your inventory in front of buyer agents, talk to us.
Seller agents at Appliscale