The IAB Tech Lab's ARTF spec puts an agent beside an SSP and lets it mutate the OpenRTB object before any buyer sees it. Named uses: identity, deal activation, fraud pre-impression. The demo I keep around for this is honest about what it is: synthetic mix, real guardrail, real gRPC. The TV is a dummy. RTBlint is the real process. ARTF stops at the auction.
That last sentence is the one that keeps getting lost.
An agent that speaks fluent gRPC can still hand you an auction that is no longer valid OpenRTB. We built RTBlint for that hop: envelope, mutations, applied object. The framework accepted the RPC. The auction was never asked. That is the whole point of putting a validator on the wire the agent already uses.
Then the auction clears, and somebody still has to play a video.
Two documents, two hops
ARTF v1.0 mutates OpenRTB. The creative that ends up in adm, in a wrapper chain, in an SSAI stitch, in a trafficking upload, is VAST. Different schema, different silent failures, different catalog.
A missing <Impression>, a Duration written as 30 instead of 00:00:30, a VPAID apiFramework on a CTV app: none of those produce a player error you can act on in the auction window. The impression is gone. The tracking may or may not fire. The discrepancy shows up in a report three days later.
Policy layers being built for agentic buying (budget caps, inventory allowlists, geo constraints, drift detection) answer was this agent allowed to do that? They do not parse the payload. I wrote about that gap on the AdCP side already. Same hole, different document.
So the pipeline that already has a gRPC channel into the host needs a second call, on a different proto, against the VAST document. That is vastlint-grpc.
ARTF host
OpenRTB bid request → RTBlint gRPC → forward or drop
VAST in adm / stitch → vastlint-grpc → quarantine or serve
Chat / IDE agent
validate_vast → vastlint-mcp (SSE or stdio)
MCP stays the right interface for Claude, Cursor, and the AAMP Buyer Agent SDK. Those callers want tools, not stubs. gRPC is the right interface for the thing that already has a channel, a deadline, and a sidecar mesh: Prebid Server Java, a Spring DSP, an SSAI stitcher, a bulk catalog sweeper, an ARTF host that is already speaking protobuf to everything else.
Why not JNI
Go embeds the Rust core in-process through CGo. That is the correct bid-path answer: a few hundred microseconds, no network hop, no extra process to page. Python and Erlang take the same shape.
Java does not. A JNI load on a Vert.x or Netty event loop is a crash domain. The JVM client is a blocking stub. The catalog lives in aleksuix/vastlint-grpc, FROM scratch, ports 50051 and 9090.
docker run --rm -p 50051:50051 aleksuix/vastlint-grpc:0.13.0
try (VastlintClient client = VastlintClient.connect("localhost:50051")) {
Verdict verdict = client.validate(xml);
if (!verdict.getValid()) {
// reject the bid, return rule IDs upstream
}
}
Reflection is on. grpcurl needs no local proto. Health is grpc.health.v1, which is what Kubernetes actually probes. The server honours grpc-timeout. Under load it sheds with RESOURCE_EXHAUSTED rather than queueing; health and reflection are exempt, so a busy instance does not look dead.
ValidateStream is the bulk path. Responses can arrive out of order; you correlate on request_id. One bad document is a StreamError on that id. The stream continues. A catalog backfill is not a reason to tear down the connection.
The wire contract is openadtech.vastlint.v1. Rule IDs are strings, not a proto enum. The catalog moved from 108 rules in April to 228 now; binding that to a slow-moving wire contract would make every new rule a breaking change. Unknown IDs in rule_overrides return INVALID_ARGUMENT. A typo that silently disables nothing is indistinguishable from a rule that never fires.
What this is not
It is not ARTF mutation RPCs. Those stay on the OpenRTB object. Claiming otherwise would be the same class of error as an agent that echoes the bid request id instead of the extension-point id: the RPC succeeded, the document was the wrong one.
It is not a replacement for in-process Go. If you can load the core in the bidder, do that. Localhost gRPC is for the languages and meshes that cannot.
It is not a chat protocol. If your caller is an LLM with a tool loop, use MCP. If your caller is already a gRPC pipeline, use the sidecar.
Runbook, Java client, k8s manifest, and the hop diagram: vastlint.org/docs/grpc.
Top comments (0)