DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

LIFECYCLE_DSP_BID_RESPONSE Can Ship Without bid_response. The Agent Has Nothing to Shade.

A DSP-side ARTF host sets lifecycle to LIFECYCLE_DSP_BID_RESPONSE, keeps the publisher bid_request on the envelope, and omits bid_response. The gRPC call completes. The agent returns BID_SHADE on /seatbid/dsp-1/bid/bid-abc. The orchestrator has no bid with that id in the payload it actually received.

That is not a shading bug in the agent. It is an envelope that names a stage without attaching the document that stage requires.

The IAB Tech Lab Agentic Real Time Framework (ARTF) puts a containerized agent beside an SSP, exchange, or DSP. The host sends an RTBRequest with a lifecycle value, a tmax budget, the OpenRTB object under mutation, and applicable_intents: the vocabulary the orchestrator is willing to evaluate. The agent answers with Mutation messages. Each mutation has an intent, an operation, a semantic path, and a typed payload.

BID_SHADE is a response-stage intent. It adjusts bid.price on a specific seat and bid id. The path grammar is explicit: /seatbid/{seat}/bid/{bid id}. There is no impression index to fall back on. If bid_response is missing, every shading path is a guess.

lifecycle is a label on the call, not proof of contents

ARTF defines lifecycle stages such as LIFECYCLE_PUBLISHER_BID_REQUEST and LIFECYCLE_DSP_BID_RESPONSE. The name tells the agent which intents are meaningful on this hop. It does not inject the matching OpenRTB member.

A response-stage call still has to carry bid_response when the agent is expected to read prices and ids from it. RTBlint reports the mismatch as artf.lifecycle.payload_mismatch:

{
  "id": "ep-1",
  "tmax": 120,
  "lifecycle": "LIFECYCLE_DSP_BID_RESPONSE",
  "bid_request": { "id": "a-1" }
}
Enter fullscreen mode Exit fullscreen mode

The envelope declares the DSP bid response stage and carries no bid_response member. Intents that belong to that stage, BID_SHADE above all, address a bid by seat and bid id. The agent is being asked to reason about an answer it was never shown.

The symmetric failure at the publisher stage is sending BID_SHADE when only bid_request is present and no bid exists yet. That combination triggers artf.mutation.intent_not_applicable together with a missing target payload. Shading before any bid is made is not a floor tweak; it is the wrong lifecycle for the intent.

applicable_intents is the other half of the contract

The orchestrator also publishes applicable_intents: which mutation types it will even consider on this extension point.

Without that list, the agent invents work the host may reject. RTBlint flags artf.intents.missing when the field is absent entirely.

When the field is present but not a JSON array, protojson refuses it. A single intent written as a bare string looks natural in hand-authored JSON and fails on the wire:

{
  "applicable_intents": "ACTIVATE_DEALS"
}
Enter fullscreen mode Exit fullscreen mode

Protobuf repeated fields serialize as arrays. The fix is ["ACTIVATE_DEALS"], even for one intent. That encoding error is artf.intents.not_array.

If the agent returns an intent outside the declared list, the mutation is dead on arrival: artf.mutation.intent_not_applicable. That is expensive inside a 120 ms tmax window.

When bid_response is present but the path still misses

Hosts that do attach bid_response can still lose shading if the path names a seat or bid id that is not in that document. BID_SHADE does not address bids by array offset. A typo in the seat name is not recoverable by scanning nearby objects.

RTBlint reports that as artf.mutation.bid_unknown. The mutation looks structurally fine. The price never changes because the target bid was never there.

How you catch it before the auction forwards nonsense

I maintain RTBlint, an open source OpenRTB and ARTF linter. Envelope checks run before mutation checks, because the lifecycle and intent list define what the response is allowed to propose.

$ rtblint validate --type artf-request rtb-request.json
FAILED (OpenRTB 2.6-202606 ARTF request envelope): 1 error(s), 0 warning(s).
- [error] lifecycle: envelope declares LIFECYCLE_DSP_BID_RESPONSE
  but carries no bid_response. (artf.lifecycle.payload_mismatch)

$ rtblint validate --type artf-response --request rtb-request.json rtb-response.json
Enter fullscreen mode Exit fullscreen mode

Pass two pairs each mutation against the request's applicable_intents and against semantic paths in the payloads actually carried. Pass three applies accepted mutations and revalidates OpenRTB so you see field-level damage (wrong metric type, deal id the exchange does not list) rather than blaming the original emit.

The same rules run in the ARTF simulator, the browser tester, MCP tools, and gRPC on openadtech.rtblint.v1. ARTF already mandates gRPC for the extension point; validating on that channel beats shelling out after the fact.

Where to read the rest of the surface

The eight intents and what each one touches on OpenRTB are laid out in ARTF mutations and bid request validation. That post is the map for checkpoint validation after every mutating stage, not only at the SSP edge.

For field-by-field context on the envelope itself, ARTF explained walks the request and response members without assuming you have read the PDF first.

RTBlint is independent of IAB Tech Lab. The rule ids above come from the public ARTF proto and the v1.0 document; they exist because lifecycle, intents, and paths are contracts you can violate while the RPC still returns OK.

Top comments (0)