{"slug": "an-open-ad-protocol-for-llm-inference", "title": "An open ad protocol for LLM inference", "summary": "Musa Aghayev released UAP, an open protocol for selling advertising on LLM inference, designed to let any inference provider sell placements and any demand source buy them without altering the model's answer or leaking context. The protocol enforces context confinement, answer integrity, and earned payment through mechanisms like answer-digest commitments, deterministic composition, and holdout testing, with a reference implementation available on GitHub under Apache-2.0.", "body_md": "An open protocol for selling advertising on LLM inference. Any inference provider can sell a placement, any demand source can buy it, and the ad cannot change the answer or take the conversation off the machine.\n\n[Specification](/musa92/uap/blob/main/SPEC.md) · [Market context](/musa92/uap/blob/main/docs/documentation/market-context.md) · draft-01 · protocol version `2026-09-02`\n\n· Apache-2.0\n\nWritten by [Musa Aghayev](https://github.com/musa92). Design questions and\ncritique are welcome in [Discussions](https://github.com/musa92/uap/discussions);\nI read all of them.\n\nAssistants now carry ads. ChatGPT began serving sponsored links below answers in February 2026, and the unit works commercially. But that is a closed platform: OpenAI's Advertiser API sells one surface, and nobody else can sell into it or buy through it.\n\nEveryone else who serves a model has no way to monetise it. The obvious workaround, sending the conversation to an ad server and pasting the winning creative into the prompt, fails twice. It leaks private context to a third party, and it hands an advertiser write access to the model's reasoning for the price of a CPM.\n\nUAP is the interoperable version, built so neither failure is expressible.\n\nAn implementation that violates any of these is not conformant, whatever else it does.\n\n**Context confinement.** Prompt text, completion text, and any identifier stable\nacross sessions never leave the serving node. Only bounded, enumerable signals\nmay be transmitted. No consent flow unlocks this.\n\n**Answer integrity.** Ad presence, identity, and price never change the answer.\nCreative never enters the model's context and never influences decoding.\n\n**Earned payment.** The serving node is untrusted. Payment follows what the\nsurface attested and the settlement layer verified, never what the node claimed.\n\n\"The ad did not change the answer\" is a claim a node has every incentive to make falsely, so it rests on three mechanisms rather than an attestation.\n\n**Ordering.** The node commits to the answer digest inside the `AdRequest`\n\n,\nbefore the exchange runs the auction. The exchange holds the commitment before a\nwinner exists, so an answer matching it could not have depended on the outcome.\nA receipt whose rendered answer does not match is not billable. Only the digest\nis sent, so this adds no context egress.\n\n**Composition.** The composer is deterministic and never calls a model. Given\nthe answer and the decision, anyone can recompute the exact bytes and compare.\nByte equality proves the output is the organic answer followed by the disclosed\ncreative, with nothing interleaved or rewritten. This needs no key and no\ncooperation from the node:\n\n``` python\nfrom uap import verify_composition, verify_answer_commitment\n\nverify_composition(rendered, answer, decision)     # (True, 'composition is exact')\nverify_answer_commitment(rendered, committed)      # (True, 'matches the commitment')\n```\n\n**Holdout.** The exchange marks a deterministic fraction of requests as holdout.\nThe node runs the identical path, no ad is served, and it still reports the\nanswer digest. Systematic divergence between served and held-out answers is\nevidence of decode influence that no per-turn check can produce.\n\nLocal decisioning has no round trip and so cannot produce the ordering proof. There the guarantee rests on composition, on the holdout, and at trust tier 2 on an attestation covering the enforcement path. The specification prices that difference rather than pretending it away.\n\nPython 3.10 or later. No dependencies.\n\n```\ngit clone https://github.com/musa92/uap && cd uap\nmake demo     # full flow in process, then the same flow over HTTP\nmake test     # 210 schema and conformance checks, links, 57 unit tests\nmake serve    # run the reference exchange on localhost:8787\n```\n\nThe first demo runs four parties with four keys through one impression, then shows five abuse cases being rejected: a replayed nonce, a swapped creative digest, a tier 0 node billed on CPM, an impression index past the pacing allocation, and a sensitive turn that produces no auction. The second does the same over a socket against the reference exchange.\n\nIn local decisioning the nonce is derived rather than issued, because there is no round trip to issue one. The exchange recomputes it from the bundle it signed, resolves the entity from the signing key rather than from anything the node sends, and derives the clearing price from the reported auction trace instead of reading it from the receipt.\n\nTwo five-minute guides: [monetise a model](/musa92/uap/blob/main/docs/documentation/quickstart-provider.md)\nif you serve one, [run a campaign](/musa92/uap/blob/main/docs/documentation/quickstart-advertiser.md)\nif you buy. Or `docker compose up`\n\nfor an exchange, a stand-in model and the\nproxy, wired together.\n\nIf you serve a model you are the serving node. If you render to a human you are the surface. They are separate because a node signing its own impression counts is a claim, not a measurement.\n\n``` python\nfrom uap import Node, Surface, ContextClassifier\n\nnode = Node(\"node.example\", \"hf:moonshotai/Kimi-K2-Instruct\",\n            signing_key=key, exchange_keys=ring)\nnode.load_bundle(bundle)                          # hourly, not per turn\n\nanswer   = model.generate(conversation)           # ads cannot reach this call\nsignal   = ContextClassifier.derive(conversation) # stays on this machine\nresult   = node.decide_local(signal, placement)   # zero network calls\ncomposed = node.compose(answer, decision)         # deterministic, not a model\n```\n\nNo call takes `conversation`\n\nand a network address in the same expression.\n\nIf you already run an OpenAI-compatible server, which vLLM, SGLang, Ollama, llama.cpp, TGI and LM Studio all are, the integration is one wrapper:\n\n``` python\nfrom uap.middleware import UAPMiddleware, ExchangeClient\n\nads = UAPMiddleware(node, surface, ExchangeClient(\"https://uax.example.com\", agent))\nads.sync_bundle()                              # scheduled\n\nresponse = ads.complete(your_client, request)  # upstream call is untouched\n```\n\n`complete`\n\ncalls upstream first and appends a disclosed sponsored block only if\none wins. Any error in the ad path returns the unmodified completion, so a\nmonetisation bug can never cost you an answer.\n\nBuying side: `conformance/vectors/valid/ad-request-full.json`\n\nis a complete\nrequest body with deals, seller metrics, GPP and DSA consent, GARM brand\nsuitability, and a two-hop supply chain. Every field names its OpenRTB 2.6\nequivalent in `source/schemas/supply/ad_request.json`\n\n, so existing demand needs\na translation layer rather than a rewrite.\n\nThe checks a buyer runs before spending are implemented, not just specified:\n\n``` python\nfrom uap import verify_chain, assess, meets_mrc\n\nverify_chain(chain, declarations)   # every hop resolves in a uap-sellers.json\nmeets_mrc(viewability)              # 50% of pixels for 1s, and the rest\nassess(receipts)                    # viewable and IVT rates, dwell anomalies\n```\n\n`verify_chain`\n\nrejects a hop absent from the named system's seller declaration,\na hop claiming a trust tier the system does not substantiate, an incomplete\nchain, a loop, and a chain in which nobody is in the payment flow. `assess`\n\nflags a dwell distribution too uniform to be human, which is how fabrication at\nscale actually shows up.\n\nFull integration guide for both sides:\n[docs/documentation/integration.md](/musa92/uap/blob/main/docs/documentation/integration.md).\n\n**Hosted.** The node sends a bounded signal, the exchange runs the auction and\nreturns a signed decision. Familiar, and the signal is constrained by a\npublished k-anonymity floor.\n\n**Local.** Signed campaign bundles sync to the node on a schedule. The auction\nruns on-device against the full private context, and batched signed receipts go\nback later. Nothing about the conversation leaves, including from the exchange's\npoint of view. This is the same architecture as Chrome's Protected Audience API,\napplied to a turn instead of a page.\n\nDraft for public comment. §14 requires two independent interoperating\nimplementations to leave draft. The serve-time core now has two, in Python and\nJavaScript, written from the specification rather than translated from each\nother, and `make interop`\n\nholds them to byte-identical canonicalization,\nsignatures, escaping, composition and predicate results.\n\nImplemented: canonicalization, signing, the targeting predicate language, the auction, the integrity boundary, receipt verification, settlement splits, and schemas for all of it.\n\nSensitive-category classification fails closed. `sensitive_category`\n\nis\nthree-valued, because a two-valued flag collapses \"found no evidence\" into\n\"confidently not sensitive\", and only an explicit false permits a turn to carry\nadvertising. A node also refuses to act on a classifier that does not declare\nitself evaluated against the sensitive taxonomy. The consequence is that the\nshipped keyword classifier monetises nothing unless the operator explicitly\naccepts the risk, which is correct and which means **nobody can run this in\nproduction without building a real classifier first**. That is the hardest\nremaining piece and it is not in this repository.\n\nThe supply service is defined in OpenAPI 3.1 at\n`source/services/supply/rest.openapi.json`\n\nand implemented over HTTP in\n`reference/python/uap/server.py`\n\n.\n\nThe buy side is implemented at the reference level: campaign and line-item management, creative review that resolves URLs against the advertiser's verified domains and scans for instruction-shaped text, forecasting as ranges with sub-floor breakdowns suppressed, a conversions endpoint that refuses serving nodes as a source, and aggregate reporting under a closed dimension set with differential-privacy noise on intent breakdowns.\n\nNot yet built: the billing lifecycle (invoicing, disputes, make-goods), RFC 9421 transport signing, and a production classifier.\n\nTwo design problems are documented rather than hidden:\nfrequency capping is enforceable only within one node, and Profile L pacing can\nstrand node revenue. Both are described in `SPEC.md`\n\n.\n\nThe parts most worth attacking are the k-anonymity floor (§6.5), bundle fetch as a side channel (§8.2), and whether trust tier 0 has any honest demand (§9.3).\n\n```\nSPEC.md               normative specification\nsource/schemas/       JSON Schema for every wire object\nsource/services/      OpenAPI definition of the REST binding\nsource/taxonomy/      intent and sensitive-category taxonomies\nreference/python/     reference implementation: exchange, buy side, settlement\nreference/typescript/ second implementation: the serve-time core a surface runs\nconformance/          vectors, positive and negative\nexamples/             end-to-end traces\n```\n\nSee [CONTRIBUTING.md](/musa92/uap/blob/main/CONTRIBUTING.md) and [GOVERNANCE.md](/musa92/uap/blob/main/GOVERNANCE.md).\nNothing in §6, §7, or §9 may be relaxed by an extension. Security reports: [SECURITY.md](/musa92/uap/blob/main/SECURITY.md).\n\nApache-2.0. Specification text is additionally available under CC-BY-4.0 so the protocol can be re-specified independently of this implementation.", "url": "https://wpnews.pro/news/an-open-ad-protocol-for-llm-inference", "canonical_source": "https://github.com/musa92/uap", "published_at": "2026-09-04 04:37:55+00:00", "updated_at": "2026-09-04 04:53:07.995355+00:00", "lang": "en", "topics": ["ai-infrastructure", "ai-policy", "ai-ethics"], "entities": ["Musa Aghayev", "UAP", "OpenAI", "ChatGPT", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/an-open-ad-protocol-for-llm-inference", "markdown": "https://wpnews.pro/news/an-open-ad-protocol-for-llm-inference.md", "text": "https://wpnews.pro/news/an-open-ad-protocol-for-llm-inference.txt", "jsonld": "https://wpnews.pro/news/an-open-ad-protocol-for-llm-inference.jsonld"}}