DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

ARTF Envelope tmax as "150" Is Not OpenRTB bid_request.tmax. The Agent Still Gets Called.

A sidecar agent mutates the publisher bid request before any DSP sees it. The host builds an ARTF RTBRequest, sets a deadline, and calls Mutate over gRPC. The agent returns mutations. The orchestrator applies them and forwards whatever OpenRTB object survives.

The failure I keep seeing is not in the mutations. It is in tmax on the envelope.

{
  "id": "ext-2026-09-26-001",
  "tmax": "150",
  "lifecycle": "LIFECYCLE_PUBLISHER_BID_REQUEST",
  "bid_request": { "id": "br-1", "tmax": 500, "imp": [{ "id": "imp-1", "video": { "mimes": ["video/mp4"], "w": 640, "h": 360 } }] },
  "applicable_intents": ["ACTIVATE_DEALS"]
}
Enter fullscreen mode Exit fullscreen mode

That file looks fine in a diff review. The sample in the spec uses 150. Someone copied the sample into a YAML template and left quotes on the number. The OpenRTB object underneath still has its own tmax: 500, which is the auction timeout in milliseconds. Two different fields, two different jobs, and only one of them is quoted.

What ARTF tmax actually measures

On the ARTF envelope, tmax is how many milliseconds the exchange allocates for the extension point itself: serialize the payload, run the agent, deserialize mutations, and decide what to apply. Reference examples cluster around 120 to 150 ms because the call sits inside an auction the host is already timing out.

OpenRTB BidRequest.tmax is the bid timeout for the whole RTB hop. Values in the hundreds or low thousands are normal there. They are not interchangeable. Pasting bid_request.tmax onto the envelope without renaming the field is a unit confusion that still parses as JSON when both numbers are bare integers.

Protobuf JSON makes the string case worse. Field 3 on RTBRequest is an integer. A quoted "150" is not an integer on the wire. Some pipelines never parse the envelope strictly; they forward the blob to an agent that treats missing deadlines as "best effort." The RPC returns OK. The orchestrator may never learn the host promised 150 ms of budget in a form the contract accepts.

RTBlint reports a string as artf.tmax.not_integer:

$ rtblint validate --type artf-request rtb-request.json
FAILED (OpenRTB 2.6-202606 ARTF request envelope): 1 error(s), 0 warning(s).
- [error] tmax: tmax is the milliseconds the exchange allows for mutations;
  "150" is not an integer. (artf.tmax.not_integer)
Enter fullscreen mode Exit fullscreen mode

Zero and negative values fail as artf.tmax.non_positive. A host that sets tmax: 0 because "we disabled the timeout" leaves the agent no time to answer at all.

When the integer is legal but the story is wrong

Even with a bare integer, magnitude matters. RTBlint warns above 1000 ms with artf.tmax.implausible. That is usually someone who copied auction seconds (30) thinking the field meant seconds, or who lifted bid_request.tmax without reading the ARTF definition. The warning does not block parsing; it flags a budget that does not match an in-auction sidecar call.

The other half of the envelope contract is what the orchestrator tells the agent it may return. An empty applicable_intents array is valid JSON and out of scope by definition: every mutation is undeclared. That is a warning, not a hard error, which matches production hosts that treat intent lists as documentation until something breaks in pass two.

How you catch it before the auction forwards

An implementation that lets a model or template emit the envelope still has to check that envelope against the ARTF contract and against the OpenRTB snapshot the exchange runs. RTBlint is that check for the OpenRTB object, the ARTF request and response envelopes, and the applied bid request after mutations (cargo install rtblint, npm install rtblint-core, MCP validate_artf_request / validate_artf_response with apply). It is independent of IAB Tech Lab and of AgenticAdvertising.org; the rules come from the public proto and the validation passes described in the ARTF material.

Run pass one on every RTBRequest at the host boundary, before gRPC leaves your network. Run pass two on RTBResponse mutations against the request you actually sent, not a cached copy from an earlier auction. Run pass three with --apply when you need to know whether a legal-looking ADD_METRICS patch broke imp[].metric[].value on the forward path.

The ARTF simulator walks the same three passes in a browser if you are debugging a single sample envelope. The longer primer is ARTF explained: lifecycle, semantic paths, and why response-stage intents need a bid_response on the wire.

Where to go next

If you host agents, log envelope tmax separately from OpenRTB tmax in your metrics. If you generate ARTF JSON from LLM tool output, type-check tmax as an integer before you call gRPC. If you inherit a sidecar from a vendor, ask whether they validate the envelope or only the mutations.

The auction only sees the OpenRTB object after your orchestrator applies patches. Getting tmax wrong does not always stop the RPC. It stops you from reasoning about whether the agent had a real deadline, which is the whole reason the field exists on the envelope in the first place.

Top comments (0)