What actually lands on an engineer's desk

The EU AI Act is effective from 2 August 2026, except for the specific provisions listed in Article 113. For a working engineering team, that sentence resolves into three things arriving at once: the transparency obligations under Article 50, enforcement powers over general-purpose AI models, and the full penalty regime. The heavier obligations attached to high-risk systems were pushed back to 2 December 2027 and 2 August 2028, which is why a lot of teams read the headlines about delay and concluded, incorrectly, that nothing is due for another eighteen months. Transparency is not one of the delayed pieces.

The AI Omnibus Regulation is what moved those dates. Its final text entered into force in July 2026, following political agreement reached on 7 May 2026; alongside extending compliance deadlines for high-risk AI systems it clarified existing AI Act requirements and introduced new rules on AI-generated intimate content. If you filed the Omnibus under "deregulation" and moved on, it is worth a second read, because the parts that were relaxed are not the parts that apply to a chat product or an image generator shipping this quarter. We walked through the enforcement timeline in our builder checklist for GPAI enforcement, and the negotiation itself in our coverage of the Omnibus deal and the deadlines it changed.

This is engineering guidance, not legal advice. Everything below is about how to build systems that make a compliance position implementable and auditable. Whether your specific product falls inside a specific obligation is a question for counsel, and this guide will point at the places where that conversation is genuinely necessary rather than pretending an article can settle it.

Stripped to its engineering content, Article 50 asks for two disclosures. Providers of AI systems intended to interact directly with people must disclose to end users that they are interacting with an AI system. Providers of systems generating synthetic audio, image, video or text content must disclose that the content has been generated or manipulated by AI. Separately, providers of general-purpose AI models must maintain technical documentation recording the training and testing process of the underlying model, plus documentation to supply to downstream providers, and must document known or estimated energy consumption.

That is four artefacts, and none of them is a document you write once. An interaction disclosure is a feature of every surface you ship. A content marking is a step in every generation pipeline. Technical documentation is an output of your training and evaluation runs. An energy figure is a measurement with a method attached. Teams that treat these as prose deliverables end up with a folder that was accurate on the day it was written; teams that treat them as build artefacts end up with something that stays true because it is regenerated every time the thing it describes changes.

Scope triage: are you a provider, a deployer, or both?

Before you build anything, work out which hat you are wearing, because the same company frequently wears two. The distinction that matters in practice is between putting a system or model onto the market under your own name and using someone else's under theirs. The table below is an engineering triage aid — a way to decide what to put in the sprint — and explicitly not a legal determination. Borderline cases, and there are many, need counsel.

What you are actually doingMost likely engineering postureFirst artefact to build
Training a general-purpose model from scratch and offering it to others GPAI model provider, and provider of any system you ship on top Technical documentation pipeline plus energy measurement
Fine-tuning open weights and exposing the result as your own API Likely GPAI model provider for your derivative; provider of the API surface Model card generation covering your fine-tune, inheriting base-model documentation
Building a chat product on a hosted third-party model Provider of the system your users talk to; deployer of someone else's model Interaction disclosure across every surface you own
Generating images, voice or copy from a hosted tool and publishing it Deployer, and the party putting synthetic content in front of people Content marking in the publishing pipeline, not in the generation tool
Embedding a vendor chatbot widget on your own site Deployer — but users experience it as your product Contractual evidence the vendor discloses, plus your own visible notice
Selling an agent from Bengaluru or London to customers inside the EU Market reach, not incorporation, is what to plan against The full set, on the same timeline as an EU-headquartered competitor

That last row is the one Indian and UK teams under-price. A start-up in Koramangala with no EU entity and a start-up in Shoreditch with no EU entity are in materially the same engineering position: if your users are in the EU or your output is consumed there, the sensible planning assumption is that you build the transparency surfaces, and you build them now rather than during a customer's procurement review. The cost of shipping disclosure into a product that is still being designed is a week. The cost of retrofitting it into a shipped product with enterprise customers and a mobile app in review is considerably more than a week.

For Indian teams there is a useful adjacency. India's Digital Personal Data Protection framework and Article 50 are aimed at different things — one at personal data, one at the artificiality of the interaction — but they converge on the same engineering primitive: a notice-and-record layer that shows a specific person a specific statement at a specific moment, and keeps evidence that it happened. Where they diverge is that a DPDP-style consent record is keyed to a data principal and a purpose, whereas an Article 50 interaction disclosure is owed whether or not any personal data is in play. Build the notice layer once, key it by surface and by user where a user exists, and both regimes read off the same tables. If you are already routing requests by jurisdiction, the same routing spine carries this; we covered that architecture in the guide to data residency and request routing for DPDP and GDPR.

Watch out

"We use a vendor, so it is their obligation" is the single most common triage error. Your users are interacting with your product. Even where the vendor carries an obligation of its own, you need evidence that their disclosure fires, and you need a fallback of your own for the day their widget fails to load or their copy changes without notice.

Disclosure surfaces: where "you are talking to an AI" has to appear

The naive implementation is a line of text in the chat header. The real problem is that a modern AI product has more surfaces than anyone remembers, and each one reaches a different audience through a different channel. Enumerate them before you write any code, because the enumeration is the hard part; the implementation is straightforward once the list exists.

SurfaceWho encounters itMinimum implementationThe trap
Web or in-app chat End user, live Persistent label in the composer chrome plus a first-turn statement Dismissible modal that a returning user never sees again
Voice agent on a phone line Caller, no visual channel Spoken disclosure before the first substantive turn, repeated on transfer Disclosure lost when the call is warm-transferred between systems
Email or ticket replies sent by an agent Recipient, asynchronously, often forwarded In-body statement in the signature block, not only in headers Quoting and forwarding strips everything except the body text
API responses consumed by another product A machine, then someone else's users Response headers and a field in the payload, documented in your API reference Your customer renders the text and drops your metadata silently
Generated media assets Anyone, anywhere, forever Visible marking plus embedded metadata plus provenance manifest The asset outlives every system that knew where it came from
Agent acting inside a third-party channel Users of Slack, WhatsApp, a partner portal Disclosure in the bot profile and in the first message of each thread Channel formatting rules quietly strip your footer

The design problem underneath all six rows is persistence. A disclosure a user dismisses once is not the same thing as a disclosure that is present. Consider the ordinary lifecycle of a support conversation: a user opens the widget on Monday and clicks through your modal; on Thursday they return via a deep link into an existing thread; the following week they forward the transcript to a colleague who was never shown anything at all. If your only disclosure was the Monday modal, two of those three people encountered an AI system with no indication that it was one.

The pattern that holds up in practice has three layers. First, an undismissable affordance in the interface chrome — small, quiet, always visible, of the same species as the "encrypted" indicator in a messaging app. Second, a statement inside the conversation itself, in the first assistant turn, so that it travels with any transcript that gets copied or exported. Third, a machine-readable signal on every response, for consumers that are not human eyes. Crucially, all three should render from one source of truth, or they will drift within a quarter and you will discover the drift during an audit rather than during a sprint.

# One source of truth for disclosure: surfaces.yaml drives every channel.
# Human-visible copy and machine-readable headers cannot drift apart
# because they are rendered from the same record.

from fastapi import FastAPI, Request
import pathlib, yaml

SURFACES = yaml.safe_load(
    pathlib.Path("compliance/disclosures/surfaces.yaml").read_text()
)
app = FastAPI()


@app.middleware("http")
async def attach_disclosure(request: Request, call_next):
    key = request.headers.get("X-Surface", "api")
    surface = SURFACES["surfaces"].get(key)
    response = await call_next(request)

    if surface is None:
        # Fail closed: an unregistered surface is a bug, not a default.
        response.headers["X-AI-Disclosure-Status"] = "unregistered-surface"
        return response

    if surface["ai_interaction"]:
        response.headers["X-AI-System"] = "true"
        response.headers["X-AI-System-Id"] = surface["system_id"]
    if surface["synthetic_output"]:
        response.headers["X-AI-Generated"] = "true"
        response.headers["X-AI-Model-Id"] = surface["model_id"]
    response.headers["X-AI-Disclosure-Url"] = surface["disclosure_url"]
    response.headers["X-AI-Disclosure-Rev"] = SURFACES["revision"]
    return response


def first_turn_disclosure(surface_key: str, locale: str) -> str:
    """Same YAML, rendered for humans. Used by chat, voice and email."""
    surface = SURFACES["surfaces"][surface_key]
    return SURFACES["copy"][locale][surface["copy_key"]]
Recommended

Write a test that enumerates your routes and fails the build when a route serves model output without a registered surface. Compliance controls that live in a reviewer's memory decay; controls that live in CI do not. This is the same discipline as keeping prompts under version control rather than in a config console — see the guide to prompt management, versioning and the staging eval loop.

Marking generated content: visible disclosure versus machine-readable provenance

Interaction disclosure tells a person they are talking to a machine. Content marking tells anyone downstream that an artefact was generated or manipulated by AI, and it has to keep telling them after the artefact has left your system entirely. These are different engineering problems and teams routinely conflate them, shipping a caption under an image and considering the matter closed.

There are three broad mechanisms and they fail in different directions. Visible disclosure — a caption, an overlay, a corner badge — is understood by every human and survives a screenshot, but it can be cropped in two seconds and is invisible to machines. Embedded metadata written into the file's XMP or EXIF blocks is cheap, standard and machine-readable, but most social platforms strip it on upload and any re-encode is likely to discard it. Signed provenance manifests, of the sort standardised by the content credentials work, carry cryptographic assertions about origin and edit history and are the most robust of the three, but they add key management, they require the consuming application to know how to check them, and they too are lost when an asset is re-encoded by a pipeline that does not understand them.

MechanismSurvives re-encodeSurvives screenshotMachine-readableEngineering cost
Visible caption or overlayYesYesNoLow
Embedded XMP/EXIF metadataUsually notNoYesLow
Signed provenance manifestOnly if the tool preserves itNoYesMedium — key management
Perceptual or statistical watermarkOften, within limitsSometimesYes, with a detectorHigh — and detector-dependent
Provenance record in your own databaseNot applicableNot applicableYes, to youLow — and the one you control

Text is harder still. An image at least has containers to write into; a paragraph of generated prose that a user copies into a document carries nothing at all. For text surfaces, the practical answer is that the disclosure has to live in the interaction and in the delivery envelope — the visible statement in the reply, the header on the API response, the signature block on the outbound email — because the characters themselves cannot hold it.

Be honest with yourself and with your stakeholders about the ceiling here. No marking scheme is robust against a determined adversary. Someone who wants to remove your mark will re-encode, crop, re-type or screenshot, and they will succeed. The obligation, as an engineer should read it, is to mark — reliably, by default, at the point of generation — not to make the marking unstrippable. What you should be able to demonstrate is that your systems attach markings automatically rather than when someone remembers to, that the markings survive your own pipeline end to end, and that you have a record of what was attached to what and when. That last item is the one nobody builds until they need it.

Pro tip

Keep an internal provenance ledger regardless of which external marking you adopt: asset hash, generating model and version, prompt or input reference, timestamp, and the surface it was published to. It costs one table. When a customer asks in 2027 whether a particular asset was AI-generated, that table answers in seconds — and it is the only mechanism on the list that no amount of re-encoding can strip.

Technical documentation as an artefact your CI produces

The GPAI documentation duty — technical documentation recording the training and testing process of the underlying model, and documentation to supply to downstream providers — is where most teams reach instinctively for a word processor. Resist it. Documentation written by hand describes the model that existed on the afternoon someone wrote it, and diverges from reality with the next fine-tune. Documentation generated from run metadata describes the model that exists.

The structure that works is a compliance directory that lives in the same repository as the training code, with templates checked in and generated output committed on release. The generated files are artefacts, not sources; the templates and the extraction logic are the sources.

compliance/
├── model-cards/
│   ├── template.md.j2            # Jinja template — reviewed like code
│   └── generated/                # committed on release, never hand-edited
│       ├── assistant-base-v1.4.0.md
│       └── retriever-v3.2.1.md
├── dataset-cards/
│   ├── support-tickets-2026q1.yaml
│   └── product-docs-en-2026q2.yaml
├── energy/
│   ├── methodology.md            # assumptions, sources, formula, revision log
│   └── measurements/
│       └── assistant-base-v1.4.0.json
├── disclosures/
│   ├── surfaces.yaml             # drives the middleware above
│   └── copy/{en-GB,en-IN}.yaml
├── downstream-pack/
│   ├── CHANGELOG.md
│   └── v1.4.0/                   # what customers actually receive
└── policy/
    └── scope-triage.md           # your reasoning, dated, with counsel's notes

The generation step belongs in the same pipeline that produces the model. Whatever orchestrates your training or fine-tuning run already knows the base model, the dataset revisions, the hyperparameters, the hardware and the wall-clock time; your evaluation harness already knows the test results. The documentation step is a matter of collecting those facts and refusing to proceed when one is missing.

# Called by the release job, after training and after evals.
# Fails the build on missing provenance — the whole point is that
# nobody can ship a model without the documentation that describes it.

import json, pathlib, subprocess
from jinja2 import Template

REQUIRED = [
    "model_id", "base_model", "training_run_id", "dataset_revisions",
    "eval_suite_revision", "eval_results", "hardware", "accelerator_hours",
    "energy_estimate_kwh", "energy_method_revision", "known_limitations",
]


def build_model_card(run: dict, out_dir: pathlib.Path) -> pathlib.Path:
    missing = [k for k in REQUIRED if not run.get(k)]
    if missing:
        raise SystemExit(f"model card blocked, missing provenance: {missing}")

    run["git_sha"] = subprocess.check_output(
        ["git", "rev-parse", "HEAD"], text=True
    ).strip()

    template = Template(
        pathlib.Path("compliance/model-cards/template.md.j2").read_text()
    )
    out = out_dir / f"{run['model_id']}.md"
    out.write_text(template.render(**run))

    # Machine-readable twin for the downstream pack.
    (out_dir / f"{run['model_id']}.json").write_text(json.dumps(run, indent=2))
    return out


if __name__ == "__main__":
    run = json.loads(pathlib.Path("artifacts/run-metadata.json").read_text())
    run["eval_results"] = json.loads(
        pathlib.Path("artifacts/eval-report.json").read_text()
    )
    card = build_model_card(run, pathlib.Path("compliance/model-cards/generated"))
    print(f"wrote {card}")

Two design choices in that snippet are worth stating explicitly. The build fails on missing provenance rather than emitting a card with blanks, because a card with blanks gets shipped and a failed build gets fixed. And the script emits both a human-readable Markdown card and a machine-readable JSON twin, because your customers' procurement teams want the former and their automated vendor-assessment tooling increasingly wants the latter. The evaluation results feeding in should come from the same suite you already run against your golden sets rather than a bespoke compliance test — if you have built that suite properly, as described in the guide to building an LLM evaluation suite with golden sets and judges, the documentation duty costs you an extra file write rather than an extra programme of work.

Every article here is written by a Verified Builder. Want your name on the next one?

AI Tech Connect lists AI engineers, founders and researchers across India and the UK — and the people hiring browse it to find them. Adding your profile is free.

Become a Verified Builder →

Energy accounting you can actually defend

GPAI model providers must document known or estimated energy consumption. The word doing the work in that sentence is estimated. Nobody expects a metered reading from a hyperscaler's substation, and a team that pretends to have one will be less credible than a team that shows its arithmetic. The deliverable is a number with a method attached, and the method is the part that has to hold up.

Instrument first. Your scheduler already emits what you need: accelerator type, count, wall-clock duration per job, and the job's association with a training run or an inference service. Capture it as a first-class metric rather than reconstructing it from cloud invoices six months later, and attach the run identifier so a figure in a model card can be traced back to the jobs that produced it. If you already export traces and metrics for your model calls, this is the same pipeline with two extra attributes — the approach in the guide to agent observability with OpenTelemetry extends to energy attribution with very little additional work.

InputHow to capture itWhere the error creeps in
Accelerator-hours by device type Scheduler events, tagged with run ID and job type Failed and restarted jobs double-counted or dropped entirely
Average power draw per device Vendor specification, or sampled telemetry where available Using peak rated power for a job that ran at partial utilisation
Host and networking overhead A stated multiplier, sourced and dated Silently omitted, understating the total
Datacentre overhead factor Provider-published figure for the specific region A global average applied to a Mumbai or London region
Regional grid intensity Published national or regional factor, with the year cited Stale factor carried forward across annual revisions
Inference energy per unit of service Sampled measurement extrapolated by request volume Extrapolating from a benchmark load that looks nothing like production

Write the method down in compliance/energy/methodology.md as a versioned document with a revision log, and have the model card reference the revision it used. The document should state the formula in plain terms, name every source, and record the assumptions explicitly: which power figure you used and why, what you did about restarted jobs, whether inference is measured or extrapolated, and what you excluded altogether. Excluding something is defensible. Excluding it silently is not.

Pro tip

Publish a range, not a single figure, wherever your inputs are uncertain, and show the high and low assumptions that produce it. A reviewer who can recompute your bounds trusts your number. A reviewer handed a figure to three decimal places with no method assumes it was reverse-engineered from a desired conclusion — and, in fairness, it usually was.

For teams fine-tuning rather than pre-training, the split is straightforward once you state it: measure what you control, inherit what you do not, and label both. Your fine-tuning runs and your inference fleet are measurable. The base model's pre-training energy is whatever the upstream provider published, cited as inherited rather than measured, with the date of the figure recorded. When the upstream provider updates its documentation, your next regenerated card picks up the new figure and your changelog shows when it changed.

The downstream-provider pack

The obligation to supply documentation to downstream providers looks like an administrative chore until you notice what it replaces. Every enterprise customer who builds on your model will eventually send you a vendor-assessment questionnaire, and every questionnaire is bespoke, arrives with a deadline, and asks for the same fifteen facts in a different order. The engineering answer is to publish those facts once, as a versioned artefact, and answer future questionnaires by pointing at it.

A pack that does its job contains the model card and its machine-readable twin; the intended-use and out-of-scope-use statement; known limitations and the evaluation results that demonstrate them, with the eval suite revision named; the energy figure with its methodology revision; the interaction and content-marking behaviour a downstream integrator inherits by default, and what they must implement themselves; input and output constraints including any content controls you apply; a version history; and a contact route for questions. Ship it at a stable URL, version it in step with the model, and keep a changelog that says what changed between versions rather than merely that something did.

Version discipline is what makes this pay off. A customer who integrated against v1.3.0 needs to know what is different in v1.4.0 without re-reading forty pages, and a regulator or auditor asking what you told customers in a given quarter needs an answer that does not depend on searching an inbox. Because the pack is generated from the same run metadata as the model card, the version numbers line up by construction, and "which documentation applied to the model we were using in March" becomes a lookup rather than an investigation.

Recommended

Treat the downstream pack as a product surface with an owner, a release process and a deprecation policy — the same as your API. Teams that do this stop losing engineering days to questionnaires within about two quarters, because the honest answer to most questions becomes a link and a version number.

Common failure modes and a pre-launch checklist

The failure modes below are the ones that recur, and every one of them is a system-design problem rather than a knowledge problem. The teams that hit them generally knew what Article 50 required; what they lacked was a mechanism that made compliance the default path rather than a thing someone remembers.

Failure modeHow it shows up in productionThe structural fix
Disclosure on the main surface only The new voice channel, the email agent and the partner widget ship without one Registry of surfaces plus a CI check that fails on unregistered routes
Dismissible one-time notice Returning users, deep links and forwarded transcripts show nothing Persistent chrome affordance plus an in-conversation first turn
Marking applied at publish time, not generation time Assets that skip the publishing path leave unmarked Mark inside the generation service; publishing merely preserves it
Documentation written by hand Accurate at v1.0, wrong by v1.2, discovered during a customer audit Generated from run metadata; build fails on missing provenance
Energy figure with no stated method Cannot be defended, recomputed or updated when inputs change Versioned methodology document referenced by every card
Vendor assumed to carry the obligation Their widget changes copy or fails to load; your users see nothing Your own fallback disclosure plus a monitored check that theirs fires
No provenance record of what was marked A later question about a specific asset has no answer Internal provenance ledger written at generation time

The pre-launch checklist is short enough to run before any release that touches a user-facing surface or a model version. Every surface that serves model output is registered, and the build fails if one is not. The interaction disclosure is present in the chrome, in the first turn and in the response headers, all rendered from one source. Generated media carries visible marking, embedded metadata and a provenance manifest where the format supports it, applied inside the generation service. Every generated asset has a row in the provenance ledger. The model card and its JSON twin were produced by CI for this exact model version, with no blank required fields. The energy figure names the methodology revision that produced it, and that revision is committed. The downstream pack is published at a stable URL with a changelog entry for this version. Your scope-triage note is dated, records who reviewed it, and is revisited when your product's shape changes.

One more time, because it matters: this is engineering guidance, not legal advice. The purpose of everything above is to make a compliance position implementable, evidenced and cheap to maintain, so that the conversation with counsel is about the genuinely ambiguous questions rather than about whether anyone can tell what your system does. As of July 2026 the transparency obligations are the near-term work, with the heavier high-risk obligations still ahead in December 2027 and August 2028 — and a team that has already built a surface registry, a provenance ledger and a documentation pipeline will find those later deadlines a matter of extending existing machinery rather than starting from a blank repository.

That is also, incidentally, some of the most transferable platform work available in this compliance cycle. Compliance-grade infrastructure is unglamorous and in short supply, and the engineers in Bengaluru, Mumbai, London and Manchester who can point at a working implementation of it are considerably easier to hire than the ones who can only point at the regulation.