DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

Publisher-Stage ARTF With bid_response on the Envelope Stages the Agent Wrong

An SSP-side ARTF host labels the extension point LIFECYCLE_PUBLISHER_BID_REQUEST, keeps yesterday's bid_response on the envelope for debugging, and forwards ACTIVATE_DEALS mutations against the publisher bid_request. gRPC returns OK. The agent believes no buyer has answered yet. The orchestrator is staring at a seatbid it never told the agent existed.

That is not a deal-activation bug in the agent. It is a reused envelope whose lifecycle name and its OpenRTB members disagree.

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 in milliseconds, the OpenRTB object under mutation, and applicable_intents. The agent returns Mutation messages with semantic paths. The orchestrator applies what it accepts and only then forwards the auction.

Publisher-stage calls run before any DSP has answered. Response-stage calls expect a bid_response member when intents such as BID_SHADE need seat and bid ids. The lifecycle enum names the stage. It does not delete extra members you left on the JSON from an earlier hop.

What the stage label actually promises

LIFECYCLE_PUBLISHER_BID_REQUEST means the extension point sits on the outbound publisher request. No DSP answer belongs on that wire yet. If bid_response is present anyway, RTBlint reports artf.lifecycle.payload_unexpected:

{
  "id": "ep-1",
  "tmax": 120,
  "lifecycle": "LIFECYCLE_PUBLISHER_BID_REQUEST",
  "bid_request": { "id": "a-1", "imp": [{ "id": "imp-1", "banner": { "w": 300, "h": 250 } }] },
  "bid_response": { "id": "a-1", "seatbid": [{ "seat": "dsp-1", "bid": [{ "id": "bid-abc", "impid": "imp-1", "price": 2.5 }] }] }
}
Enter fullscreen mode Exit fullscreen mode

The envelope declares publisher bid request stage and carries a full bid_response. At that stage no DSP should have answered. The validator treats it as a warning, not a hard error, because a private host might deliberately pass prior context. For a default orchestrator, the warning is the point: the agent's model of where it sits in the auction is wrong even when protobuf JSON parses cleanly.

The inverse failure, response stage without bid_response, is artf.lifecycle.payload_mismatch. That one blocks shading paths that need a bid id the envelope never included. Together they are the same contract: lifecycle tells the agent which intents make sense; the carried OpenRTB members have to match that story.

Reusing one JSON blob across hops is how both show up in production. A team logs the full auction state, copies the object into the next ARTF fixture, and forgets to drop bid_response when the lifecycle moves back to publisher stage. gRPC still accepts the bytes.

tmax on the same envelope is a separate unit trap

ARTF tmax is milliseconds budgeted for the mutation RPC inside the auction, not the OpenRTB bid timeout copied from bid_request.tmax. Reference samples use roughly 120 to 150 ms. Values above 1000 ms trigger artf.tmax.implausible: the extension point runs inside an exchange timeout, and a budget measured in seconds usually means someone pasted an auction limit without converting units.

That warning does not fail the envelope by itself. It pairs badly with a mislabeled lifecycle: you can burn seconds of agent time on a call that still carries the wrong stage label and a ghost bid_response.

How you catch it before the orchestrator applies the wrong mutations

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

$ rtblint validate --type artf-request rtb-request.json
FAILED (OpenRTB 2.6-202606 ARTF request envelope): 0 error(s), 1 warning(s).
- [warning] bid_response: lifecycle is LIFECYCLE_PUBLISHER_BID_REQUEST, which runs
  before any DSP has answered, yet the envelope carries a bid_response.
  (artf.lifecycle.payload_unexpected)
Enter fullscreen mode Exit fullscreen mode

If the document never parses, nothing downstream runs. Truncated fixtures and log line limits show up as artf.payload.invalid_json before lifecycle logic executes.

Pass two on responses pairs each mutation against applicable_intents and semantic paths in the payloads actually carried. Pass three applies accepted mutations and revalidates OpenRTB so field-level damage appears on the request the exchange will see.

The same rules run in the ARTF simulator, MCP tools, and gRPC on openadtech.rtblint.v1. ARTF already mandates gRPC for the extension point; validating on that channel beats discovering stage skew only after deals activate against the wrong document.

Where to read the rest of the surface

ARTF explained walks the request and response members without assuming you read the PDF first. The docs index links every ARTF rule id, including lifecycle, tmax, and mutation path checks, if you want stable ids in CI.

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 and payloads are contracts you can violate while the RPC still returns OK.

Top comments (0)