Register a Workload
Registering a workload records which Blueprint it follows and which ServiceAccount it runs as. It takes under five minutes and asks only for intent — never a file path, an executable, a network range, or a port. Registration sets the workload's lifecycle and never its trust: trust stays derived from evidence.
Workload Trust is available only in private, self-managed AIControls installations, on premises or in a cloud cluster that you run. It needs a dedicated cluster that you run, with Kyverno, Kyverno Runtime, and cluster OIDC and RBAC access in place. Nirmata-hosted (SaaS) workspaces don't include it. See Feature availability.
What you are declaring
A workload's intent is a short statement about what it is and what it touches. The Blueprint carries it: a workload's effective intent is its Workload Blueprint's profile and its Runtime Binding's access levels, with the sections the workload's override sets replacing them. The workload keeps only its own display name. See What a workload uses.
| Field | What it answers |
|---|---|
| Name | What its owners call it. It need not match the Kubernetes object name. Per workload. |
| Type | What kind of thing it is. See the types. From the Blueprint. |
| Purpose | One sentence saying what it is for. From the Blueprint. You don't enter it for a workload. |
| Autonomy tier | How much a person oversees what it does, from T1 (human-directed: a person reviews every action) to T4 (fully autonomous). From the Blueprint. Once registered, the workload's identity takes this tier: it's what object.agent.autonomyTier reports for its requests. If the identity already has a different tier assigned on the Identities page, its requests use the stricter of the two until you resolve the conflict. See Where the tier comes from. |
| AI access | Which models and MCP tools it uses. The Blueprint's one Access Binding grants them. The workload's override can add references to other bindings and blocks that narrow them. Intent has no model or MCP section. |
| Data classification | The highest data classification it may access: public, internal, confidential, or restricted. Optional. It comes from the Blueprint, and an override can change it. In the API, this is dataClassification. See Data classification. |
| Runtime | One access level each for network, files, programs, and the Kubernetes API. In the API, each is a section's access value, also called its tier. They come from the Blueprint's Runtime Binding, unless the override replaces a section. |
You are never asked for a path, a binary, a CIDR, or a port, and this is deliberate. Those are facts about the environment, not about the workload: the same workload is correct in staging and wrong in production, so nobody operating it can state them honestly, and a hand-guessed allowlist enforced in the kernel is an outage rather than a control. Your platform team sets them once for the cluster as Environment defaults in Settings → Workloads, and a Runtime Binding can override them. Your access levels expand against those values.
The workload types
Each type is defined by its relationship to a model and to action:
| Type | What it means |
|---|---|
| agent | Calls an LLM and acts on the response — tools, MCP calls, subprocesses. |
| assistant | Human-facing and conversational. Calls an LLM, and does not act on the response on its own. |
| mcp-server | Exposes MCP tools to other workloads. It may call no LLM at all. |
| model-service | Serves model inference to callers. |
| notebook | Interactive compute driven by a person. |
| other | None of the above. Say what it is in the Purpose. |
Type is descriptive and gates nothing. How much latitude a workload has is autonomy, and what it may reach is the access tiers. Policy should key on those, never on type — gating on type == agent would assert that every agent is equally trusted, which is the claim autonomy exists to make separately.
There is no mcp-client type: a workload that consumes MCP already shows it through its MCP grants in Access Bindings, and two ways to state one fact eventually disagree.
The tier vocabulary
Intent has four tiered sections: network, files, exec, and kubernetes. Intent has no llm or mcp section. Access Bindings are the only way to grant models and MCP tools, and the Blueprint's Access section selects or creates its one binding. The schema refuses intent.llm and intent.mcp.
| Section | Tiers | What each means |
|---|---|---|
| network | none, cluster, llm-providers, internet | llm-providers resolves to the AIControls gateway, never to a provider hostname. internet compiles no restriction and is reported as high risk. |
| files | none, own-volumes | none is the platform runtime baseline; own-volumes adds everything under the container's own volume mounts and working directory. The agent's projected token mount is readable under both tiers, so you don't add it to baseFiles, with one exception: an exact sensor can't match a directory mount, and AIControls holds enforcement until you fix the mount. See The AIControls token mount. Means "may open" — read and write are not separable. With the bundled sensor, a tier whose expanded values include a directory, such as a volume mount, compiles no rule; an all-exact expansion still compiles. See the sensor path grammar. An enforced files tier blocks kubectl exec by default. See kubectl exec is blocked by default. |
| exec | none, shell | none is the container's own entrypoint (command[0] of the running pods); shell adds the platform interpreter set. Only the named binaries may execute; their arguments are recorded, not restricted. When the pod sets no command, AIControls reads the entrypoint from the image config if the registry allows an anonymous read (an outbound request from AIControls to the registry, which you can turn off), and follows symlinks so the rule names the program the kernel reports. If it can't determine the program, no exec rule compiles, and the readiness rule for the RuntimePolicy shows Review with "AIControls couldn't determine the program this container runs". Set command in the pod spec to the absolute, symlink-resolved path, for example /usr/bin/python3.15. See Exec tiers need an absolute entrypoint. Enforcement blocks kubectl exec of a shell under none; set allowKubectlExec: true on this section to allow it. See kubectl exec is blocked by default. |
| kubernetes | none, read | A declaration of expected use, never a grant. What the workload can do is still its ServiceAccount. |
An absent section is not the same as none: absent declares no constraint at all, which is the widest value there is.
What a tier can and cannot enforce
The platform enforces as strictly as the runtime genuinely supports, and monitors everything else. Where a tier cannot be enforced faithfully, it still produces the monitoring half rather than silently promising a restriction it cannot deliver — and the response to your registration names what is enforced versus what is only recorded, so a workload that reaches verified is never read as more constrained than it is.
An exec tier produces two policies
Declaring an exec tier compiles to two runtime policies, not one:
- an enforce-capable allow list of binaries, and
- a separate monitor-mode policy that records the arguments each binary is invoked with.
They cannot be one object. Argument-level detection is only expressible as a monitoring predicate, and the runtime refuses a monitoring predicate on an enforcing policy. Splitting them is what keeps adding enforcement from silently removing the argument-level detection that shadow-AI discovery depends on — a trade that would look like a stronger control while the findings it replaced quietly stopped arriving.
The practical reading: only the named binaries may execute, and what they are invoked with is recorded, not restricted. /bin/sh -c '<anything>' is one binary, so a workload permitted to run a shell can run anything through it. That still makes exec: none a real and useful narrowing — most workloads never run a shell at all — but it is not a sandbox.
kubectl exec is blocked by default
When a workload has an enforced exec: none tier or an enforced files tier, kubectl exec into its pods fails by default. exec: shell already permits the platform interpreters, so on its own it does not block a shell.
- Under
exec: none, a shell such asshis not the container entrypoint, so the exec allow list denies it. - Under an enforced files tier, the container runtime's exec setup opens
/etc/passwd,/etc/group,/proc/<pid>/setgroups, and/proc/<pid>/task/<tid>/fdinside the container, and the open allow list denies them.
To keep kubectl exec working for debugging, select Allow kubectl exec in the Programs card of the Runtime section: in the Blueprint editor for the Runtime Binding that a fleet shares, or under Customize for this workload for one workload. You can also set allowKubectlExec: true on the exec section of the Runtime Binding's levels (for a fleet) or of the workload's override (for one workload). The option needs an exec tier, so an intent with only a files tier must also declare one, such as exec: shell. The option is off by default and changes nothing unless you set it. When it's on:
- The exec allow list adds the platform interpreters, as
exec: shelldoes. - When the files tier compiles a rule, the open allow list adds the platform exec-setup files (
execSetupFilesin the Runtime Binding). - AIControls shows the risk factor kubectl exec allowed — the agent can also run a shell and, with the default exec-setup files, open every process's
/proc/<pid>/setgroupsand/proc/<pid>/task/<tid>/entries in the container as a warning in the Programs card when you select the option, in the wizard's Review step, and on the workload's detail page and drawer while the option is on. The enforcement review and the API's risk factors carry the same text, and the registration's risk note names the procfs values the workload's Runtime BindingexecSetupFilesactually carry.
The runtime sensor's rules apply per container, and it cannot tell a kubectl exec session from a process the workload starts. Allowing kubectl exec therefore also lets the workload itself run a shell. Turn it on only for workloads you need to debug in place.
Turning the option on in an override widens the workload beyond its Blueprint, so it needs an explicit acknowledgment. Turning it on in a Runtime Binding applies to every Blueprint and workload that uses the binding. The impact preview flags kubectl exec allowed in its Risks column for each workload that gains it, and you select Confirm and save. See Review and restore a binding change.
runc opens two per-process procfs paths while it sets up a kubectl exec session:
/proc/self/setgroups, which the kernel resolves to/proc/<pid>/setgroups. The default exec-setup files allow it as/proc/*/setgroups./proc/thread-self/fd, reopened through a private procfs mount in runc 1.2 and later, which resolves to/proc/<pid>/task/<tid>/fd. The default exec-setup files allow it as/proc/*/task/*/**, which AIControls renders as the sensor's directory prefix/proc/*/task/*. The sensor accepts no narrower form, such as/proc/*/task/*/fd.
Both wildcards match any process and thread, not only the kubectl exec session, so the allows also cover every other process's /proc/<pid>/setgroups and /proc/<pid>/task/<tid>/ entries in the same container. AIControls adds them only to a workload that allows kubectl exec; a Runtime Binding refuses /proc/*/task/*/** in baseFiles. Both wildcards need kyverno-runtime v0.1.4 or later. If you run your own older sensor, set execSetupFiles without them.
The other limits worth knowing
- File rules match the accessing binary and the path, never a call's arguments, and
opentakes a single value list — so read and write are not separable.files: own-volumesmeans "may open paths under these mounts", never "may write but not read". execandfilesenforcement need the open/exec hooks. They need bpffs mounted at/sys/fs/bpfon each node, which most distributions do by default but kind and some minimal node images don't. Where the hooks are missing, that whole class of evidence is structurally unavailable — which is a different fact from "nothing was seen", and is reported as such rather than as a clean result.- Domain restriction is address-level, and there is no SNI. The runtime learns the address-to-name binding from the pod's own cleartext DNS answers and from Service informers. DNS-over-HTTPS, DNS-over-TLS, a cached answer, or a hardcoded literal address all degrade the match to address-only.
network: clusteris the strongest tier for this reason: cluster Service names are exact and come from informers rather than from an answer that has to be observed. - A kernel CIDR must be
/24or narrower. The current runtime sensor expands a CIDR into individual IPv4 addresses and cannot program a broader prefix. AIControls rejects/23,/16, and/12targets during compilation, naming the target, rather than applyingdeny: ["*"]without the intended allowance. An exact fully-qualified Service name such askube-dns.kube-system.svc.cluster.localremains valid and follows the Service's ClusterIP and ready endpoints, but a list of Service names is not a silent replacement for a tier intended to permit the whole cluster network. Appliedalone is not verification. The sensor reports separately whether it loaded a policy and whether every target was programmable. Verification remains Not verified unless bothAppliedandTargetsValidareTrueon every generated runtime policy. A missing or unsupported target keeps Enforcement Enable pending, with the sensor's reason shown.- No kernel rule carries a port. Declared ports constrain the gateway only.
- AIControls compiles no IPv6 network value. The network values AIControls compiles are IPv4 addresses and CIDRs, domain names, and cluster Service names, so you can't allow an IPv6 destination. What happens to IPv6 egress depends on the sensor version. kyverno-runtime v0.1.4 and later, the version the chart bundles, match IPv6: an enforced network rule's default deny blocks IPv6 egress that no value allows, a domain allow covers only the IPv4 addresses the name resolves to, and a cluster Service name also covers the Service's IPv6 addresses on a dual-stack cluster. On a dual-stack cluster, an enforced workload therefore loses IPv6 paths to the domains it allows. kyverno-runtime v0.1.3 and earlier neither restrict nor observe IPv6 egress. Either way, the enforcement review lists IPv6 as a standing coverage gap on every workload with a network rule, and an empty findings list is evidence about IPv4 alone. IPv6 compilation is tracked in #3452.
- Protocol is never restricted. No tier implies it, and AIControls never renders a protocol rule.
Why network: llm-providers resolves to the gateway
It expands to the AIControls gateway plus in-cluster destinations — never to a provider hostname. Allow-listing api.anthropic.com would whitelist exactly the bypass this plane exists to catch: direct provider egress would stop being a finding.
Because the expanded values are in-cluster names, the negative-form DNS observation the tier produces still reports any provider the workload resolves directly. The tier permits the gateway and keeps the bypass visible.
Identify the workload
A declaration attaches to a workload by its tuple: namespace, kind, and name. Kind is whatever owns the pods — Deployment, StatefulSet, CronJob, or your own controller's kind. There is no allowed list, so a workload a custom operator manages registers like any other.
You can register a workload before you deploy it. Until something is observed running, its lifecycle reads pending.
Register through the intake wizard
The wizard asks for a Blueprint, an identity, a name, and a mode. The Blueprint holds the workload's Purpose, intent, and access, so you answer the section questions only when this workload differs from its Blueprint.
Open Inventory → Workloads and select Register a workload. A Register… action on a workload row, drawer, or detail page skips the next step.
Select workload: choose its namespace, then type its kind and object name into the cascading fields. When AIControls can list the cluster's namespaces, Namespace is a searchable dropdown of every namespace in the cluster. Otherwise it accepts free text and suggests namespaces from discovery and existing registrations. Name suggestions and the results table update from a server-side search as you type.
Every field accepts free text, so a custom controller kind or a workload that hasn't rolled out yet needs no separate mode. A workload without runtime evidence appears as Registered · Pending until it is observed.
AIControls checks the tuple before you continue. If it already has a registration, the wizard blocks the create flow and offers Edit registration. See If the workload is already registered.
Blueprint: the default Blueprint is preselected and tagged Default. A link can preselect another one. Choose another Blueprint from the list if one fits better. A summary card shows the Blueprint's Purpose, its profile and data classification, its Access Binding and Runtime Binding, and its starting mode.
Where observed behavior exceeds the Blueprint, the step says so, for example Observed: runs /bin/sh. Blueprint: exec: none. Choose a wider Blueprint, or select Customize for this workload. See Customize for this workload.
Identity: the ServiceAccount that AIControls binds,
k8s://<namespace>/<serviceaccount>. It's the ServiceAccount that discovery observes the workload's pods running as. If discovery hasn't observed one yet, enter the ServiceAccount the workload runs as. AIControls never binds a guessed or wildcard identity. A workload that runs as thedefaultServiceAccount shows a warning, because every Pod in the namespace that runs asdefaultshares its access. The step also shows read-only identity evidence: the identities last attributed to the workload. When the Blueprint answers Acts on behalf of a user, an Expected credential note says what the workload presents to the gateway: a virtual key when it acts as itself, or a user's token, such as a personal access token, when it acts for a user. Either way, the identity you bind is the workload's ServiceAccount. A delegated user is an extra subject on each call, not a second identity. The note is advisory: AIControls doesn't enforce the credential type yet.Name: what you call the workload, its Owning team, and optionally its image. This is per workload. Everything else comes from the Blueprint.
Owning team is required. Pick a team from Settings → Teams; when no teams are defined there, type the team's name. AIControls records the team on the agent's identity as trusted metadata. When the agent isn't acting for a user, it matches Access Binding grants to that team, and its usage is attributed to the team; when it acts for a user, it matches the user's team and group subjects, and the user's primary team, if any, is its team. A grant that names a route steers the agent's requests; see Access Bindings steer routing.
Mode: choose Learn first (Audit) or Trusted: enforce now. See Choose the mode. The step also shows what the default access policy does in Enforce with a call that no referenced Access Binding grants.
Review: check the workload, Blueprint, identity, name, owning team, and mode, and the effective intent. Each answer names its source: Blueprint, Runtime Binding and its name, or This workload when you customized it. The expansion preview shows what each runtime answer expands to against the cluster's environment values and the workload's running pods, and the Traffic routing panel shows LLM, MCP, and TLS routing evidence.
Select Register. If it's unavailable, the message next to it names what's missing. AIControls creates the registration and binds the identity in one transaction: either everything is saved, or nothing is. You land on the workload's AI access tab.
Every registration names a Blueprint, so there is no registration without access. To start with nothing granted, register in Learn first against the default Blueprint.
Registration, policy, and enforcement changes are controller-local. If an inventory row belongs to another cluster, you can review its evidence, but AIControls disables those actions. Open the workload in its owning cluster to make changes.
Identity and routing evidence stay outside the registration. Verified via proxy means AIControls observed a routed call for this workload; it does not mean an access policy matched, a call was permitted, Enforce mode is active, or the workload is Protected.
Customize for this workload
Customize for this workload is a link on the Blueprint and Review steps. It adds a Customize step with the same sections as the Blueprint editor, minus Name and Review, prefilled from the Blueprint: About (type and autonomy tier), AI access (the workload's Access Binding references and blocks), Data (data classification), and Runtime (the Network, Files, Programs, and Kubernetes API cards).
Each section tab and each field shows how it compares with the Blueprint:
| Tag | Meaning |
|---|---|
| Inherited | The same as the Blueprint. |
| Narrower | Permits less than the Blueprint, for example network: none against llm-providers, or fewer MCP tools. No acknowledgment is needed. |
| Wider than Blueprint | Permits more: a wider access level, a model, upstream, MCP server, or tool the Blueprint doesn't list, a higher autonomy tier, such as T2 against T1, a higher data classification, or Allow kubectl exec. Select the acknowledgment checkbox under the field to keep it. |
| Differs from Blueprint | Type. It's descriptive, so it neither narrows nor widens. |
A changed section becomes the workload's override, and it replaces the Blueprint's whole section. Reset to Blueprint starts over, and Stop customizing drops the override. A tab with an unacknowledged widening shows a red dot, and Next names each widening you still need to acknowledge. The Review step lists the widenings again, from the server's own check, each with its checkbox.
While any field is wider than the Blueprint, Trusted: enforce now is unavailable: register in Learn first, review, and then switch the workload to Enforce. AIControls records each widened field in the configuration audit.
The section questions work like this:
- AI access: the tab shows the access Inherited from the Blueprint's Access Binding, and a This workload card. In More Access Bindings for this workload, choose other Access Bindings to reference in addition to the Blueprint's. In Block for this workload: models and Block for this workload: MCP tools, add blocks that narrow a grant. These edit the override's
bindingRefsanddeny. In the onboard API, they areoverrides.access. To change the grants the Blueprint gives every workload that uses it, edit the Blueprint's Access Binding instead. - Data: choose the workload's data classification ceiling. A lower class narrows; a higher one widens and needs an acknowledgment. When the Blueprint has a ceiling, an override can't remove it. The tab lists the granted MCP tools classified above the ceiling as a read-only warning. In the onboard API, this is
overrides.dataClassification. - Runtime → Network, Files, and Kubernetes API: choose the access level. See What the Runtime cards mean.
- Runtime → Programs: choose the access level. To keep
kubectl execworking while the workload is enforced, select Allow kubectl exec. See kubectl exec is blocked by default. Each Runtime card shows an enforcement tag with its limits, and the AI access tab and the Network card show read-only routing evidence with the configuration to use:OPENAI_BASE_URLorANTHROPIC_BASE_URLfor LLM traffic,MCP_SERVER_URLfor MCP traffic, andHTTP_PROXYplusHTTPS_PROXYfor network egress. An unavailable read stays Unknown.
Where AIControls has evidence, each field shows where its value came from: Declared (a label, an annotation, or an existing registration) or Inferred (observed behavior, with the evidence one click away). A field AIControls has no evidence for says why, in grey text:
| Reason | What it means |
|---|---|
| Needs cluster posture access. | The Kubernetes API answer. AIControls reads RBAC only when workload RBAC evidence is on and its grant is present. |
| No gateway traffic attributed to this workload | Model or MCP traffic. The gateway attributes traffic only to an identity bound to one of the workload's running pods, and none has called through it yet. |
| Not observed yet. No running pod or sensor evidence in the last 7 days. | Network, files, or programs. No running pod or runtime sensor has reported this behavior yet. |
| Not inferable. Declare it. | Autonomy always, and type when nothing describes the workload. |
| Evidence unavailable | AIControls could not query one or more evidence sources. Select or focus the label to see the source reasons. |
| No evidence available: reason | AIControls could not produce a suggestion at all, for the reason shown. |
Access comes from the Blueprint's Access Binding and from the override's own references.
Choose the mode
| Choice | Mode | What happens |
|---|---|---|
| Learn first (Audit) | Audit | The Blueprint's Access Binding grants access. Calls that nothing grants follow the default access policy and become recommendations on the AI access tab. Promote the ones the workload needs, then switch it to Enforce. |
| Trusted: enforce now | Enforce | The workload starts in Enforce. The workload's blocks stop matching calls from its first call. With the workload unmatched action set to Deny, a call that no referenced Access Binding grants is blocked too. |
Start with Learn first (Audit) for a workload you don't know well. Trusted: enforce now needs an effective grant: a workload that has a coherent Pod-bound identity, and an enabled allow-mode Access Binding, either the Blueprint's or one the override references, with at least one server, tool selector, Toolset reference, or model selector. An empty binding doesn't count. Enforce with no grants would grant nothing beyond the default access policy, so the step explains why enforcing now is unavailable and offers Learn first. A new default Blueprint has no grants until you add them, for example when the setup wizard's Model step grants the model. It's also unavailable while the workload is wider than its Blueprint. "Trusted" names only this onboarding choice. Everywhere else, the mode is Learn first (Audit) or Enforce.
Learn first doesn't shadow a restrictive unmatched action. If the default access policy holds or denies unmatched MCP or LLM calls, the step says so, and those calls are still held or denied in Audit unless you turn on the audit unmatched override on the AI access tab. They still become recommendations. The workload unmatched action is different: Learn first always shadows it, so a Deny there doesn't block calls until you switch to Enforce.
If the workload is already registered
Registering never replaces an existing registration. If another administrator registered the same workload after you opened the wizard, AIControls saves nothing and shows This workload is already registered. with an Edit registration action. If a step fails for another reason, such as an identity that is already bound to another workload, the step shows Nothing was saved and the reason, and you can fix it and select Register again.
A Discovered row on the Workloads table also offers a Register… action directly from its row menu, prefilled with that row's namespace, kind, and object name.
Register from a pipeline
A pipeline registers a workload with POST /api/v1/workloads/onboard, the same create-only operation the wizard uses. There is no declaration-only registration API: POST and PATCH /api/v1/workloads are removed, because a registration always derives its intent from its Blueprint.
Register and give access from a pipeline
POST /api/v1/workloads/onboard is the API form of the wizard's Register button. It registers the workload, binds its ServiceAccount, and attaches it to a Workload Blueprint in one audited transaction:
curl -X POST "https://aicontrols.example.com/api/v1/workloads/onboard?cluster=$CLUSTER_UID" \
-H "Authorization: Bearer $AICONTROLS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"namespace": "production",
"kind": "Deployment",
"name": "pr-review-agent",
"displayName": "PR review agent",
"team": "platform",
"blueprint": "agents",
"overrides": {
"intent": { "network": { "access": "cluster" } }
},
"acknowledgeWidening": true,
"identityKey": "k8s://production/pr-review-sa",
"mode": "audit"
}'
Requires an administrator token. cluster is the UID of the cluster's kube-system namespace, and it must be the cluster this AIControls controller runs in.
| Field | Meaning |
|---|---|
displayName | Required. The workload's name. |
team | Required. The owning team: a team ID from Settings → Teams, or any name when no teams are defined there. A missing or empty team returns 400. |
overrides.intent | Optional. Intent sections that replace the Blueprint's and its Runtime Binding's, in the intent shape without name or purpose. An unknown field is refused. |
overrides.access | Optional. The workload's own override: bindingRefs, deny, and auditUnmatchedOverride. This is what the wizard's Customize AI access tab edits. Omit it for none. |
overrides.dataClassification | Optional. The workload's data classification ceiling. |
acknowledgeWidening | Accepts an overrides value that widens the Blueprint. Without it, a widening is refused. |
identityKey | Optional when discovery observes the workload's ServiceAccount. Then AIControls binds the observed one, and a different identityKey is refused. Required otherwise. |
blueprint | A Blueprint name or ID. Omit it to use the default Blueprint. |
mode | audit for Learn first, or enforce for Trusted: enforce now. |
The request has no purpose and no blueprintRevision: the workload inherits the Blueprint's Purpose, and Blueprints have no revisions.
The response is 201 with the registration and the workload's access. Every refusal is a 409 that writes nothing:
code | Cause |
|---|---|
registration_exists | The workload is already registered. The response carries editPath. Edit the registration instead. |
identity_changed | identityKey differs from the ServiceAccount discovery observes. |
identity_unobserved | Discovery has no ServiceAccount for the workload, and the request has no identityKey. |
identity_conflict | The ServiceAccount is already bound to another workload in the cluster. |
identity_binding_conflict | The workload already has an active identity binding for another ServiceAccount. Retire it first. |
trusted_requires_references | enforce with no effective grants: the Blueprint's Access Binding and the override's references grant nothing. An empty binding doesn't count. |
default_blueprint_missing | The request names no Blueprint, and no default Blueprint is configured. |
cluster_not_local | cluster is not this controller's cluster. |
widening_unacknowledged | overrides widens the Blueprint and acknowledgeWidening is not set. The response lists widenings: each field's section, before and after values, and risk factors. |
trusted_widening | enforce with an overrides value that widens the Blueprint. Register in audit, then switch to Enforce after review. |
A retry after a lost response returns registration_exists. It never creates a second registration or binding.
Bind the identity and set the override
A registration grants no access by itself. The wizard binds the identity when it registers the workload. To change an existing workload's identity or override, use these two administrator-only API calls. Both need the cluster query parameter, the UID of the cluster's kube-system namespace. AIControls refuses them with 409 cluster_identity_unavailable when they name the controller's display-name fallback key instead of that UID.
Bind the identity:
PUT /api/v1/workloads/{namespace}/{kind}/{name}/identity-binding?cluster={cluster}with{"identityKey": "k8s://production/pr-review-sa", "rung": "pod_uid", "expectedRevision": 0}. See Bind a workload identity.Set the override:
PUT /api/v1/workloads/{namespace}/{kind}/{name}/access?cluster={cluster}with the workload's override inaccessDelta, the currentmode, and theexpectedRowVersionandrowTokenthatGET .../accessreturns. This route writes only the override. It can't switch the workload to Enforce: a request that tries returns409 mode_switch_required. See Switch a workload to Enforce.
After you register, the AI access tab on the workload detail page shows the workload's Blueprint, its two bindings, the effective access, and the mode.
After you bind a pod_uid identity, AIControls denies the workload's calls until the registration names a Blueprint. Workloads without a pod_uid binding, including those with only a virtual_key binding, keep using the matching legacy Access Bindings and the default access policy. See Workload Blueprints and Workload Registrations.
Update a registration
Edit a registration from its detail page or its row menu. The wizard is titled Edit registration and has three steps: Name, Customize, and Review and save. It opens on Review and save. To change the workload's intent, select Customize for this workload: the section tabs open prefilled from the workload's effective intent, compared with its Blueprint. To change the intent of every workload that uses the Blueprint, edit the Blueprint instead.
Save writes the workload's override through PUT /api/v1/workloads/{namespace}/{kind}/{name}/access?cluster={cluster}. The request carries these fields:
| Field | Meaning |
|---|---|
accessDelta | Required. The workload's override: bindingRefs, deny, auditUnmatchedOverride, intent, and dataClassification. Send {} for no override. |
mode | Required. The workload's current mode, audit or enforce. The request can't change the mode. |
expectedRowVersion and rowToken | Required. The registration's rowVersion and rowToken, which GET .../access returns in registration. |
displayName | Optional. The workload's name. |
team | Optional. Changes the owning team. When sent it must be a team, as for team on onboarding: an empty value returns 400, so a team can be changed but not removed. |
blueprintId | Optional. Moves the registration to another Blueprint. Empty keeps its Blueprint. |
acknowledgeWidening | Accepts a change that widens the Blueprint or the workload's effective intent. |
Without acknowledgeWidening, a widening returns 409 widening_unacknowledged with the widenings. A stale expectedRowVersion or rowToken returns 409 conflict. A request with mode set to enforce for a workload that isn't in Enforce returns 409 mode_switch_required. The write applies at once, with no preview. A workload in Enforce takes an override change on the next call, so review the change first: a block or a narrower level takes effect immediately.
Preview a registration without saving it
POST /api/v1/workloads/expand is the dry run. Like every workload route, it is admin-only, because a draft can name any Blueprint. It takes the workload's tuple (namespace, kind, and name) and an optional draft. It doesn't accept declaration. The draft fields are:
blueprint: a name or ID. The default is the workload's Blueprint, else the default Blueprint.override: intent sections. The default is the workload's current override.displayName.
The response adds blueprint, intent (the effective intent that it expanded), and widenings. The registration GET returns the effective intent as intent, with the Blueprint's profile, the Runtime Binding's id, name, and levels, the override, and the effective result. The Runtime Binding is read-only here: GET /api/v1/workloads/{namespace}/{kind}/{name}/runtime-binding returns the effective binding, and a workload has no Runtime Binding selection of its own.
Set the team on a registration that has none
A registration made before the owning team was required has no team. The Workloads list, the workload's detail page, and the identity's Workload card flag it No team. Its agent still runs and still routes by model. Calls the agent makes on its own match no team subject in Access Bindings and have no team attribution. Calls it makes for a user match the user's team and group subjects, and are attributed to the user's primary team.
- In Inventory → Workloads, select Set team next to the flag, or open the workload's row menu and select Edit registration….
- On the Name step, pick the Owning team.
- Select Save. The flag clears, and the agent's next call carries the team.
Withdraw a registration
A registered row's row menu on the Workloads table offers Edit registration… and Withdraw. You can also send DELETE /api/v1/workloads/{namespace}/{kind}/{name}.
The workload keeps running, but its Registration status becomes Discovered unless another user-created registration exists. Build declarations and cluster observations remain available as separate evidence.
Unregistered workloads
A Kubernetes workload that has no registration doesn't get AI access through the gateway. When it calls a model or an MCP server, the unregistered workload mode decides what happens:
- In Enforce, the default for new installations that don't preset a mode, the call is denied. The caller receives
workload_not_registered: workload <namespace>/<kind>/<name> is not registered with AIControls; register it in Inventory > Workloads to get AI access. - In Audit, the default for installations upgraded from a release without this mode, the call is allowed and recorded.
Either way, AIControls records a rolled-up security event and lists the workload in Inventory → Workloads as Discovered and Suspected, with the gateway call as its evidence. Developer sessions, personal access tokens, and human users aren't affected. A call from a token that isn't bound to a Pod, or from a deployment where AIControls has no live Pod resolver, keeps its existing behavior. A Pod-bound call whose Pod lookup fails is handled as described below.
These cases need AIControls to read Pods. The Helm chart grants that through workloadIdentity.podRead.enabled: true or through the Runtime Controls permissions. If the Pod lookup is refused by RBAC, AIControls can't tell which workload a caller belongs to, so it fails closed: it denies the call as workload resolution unavailable. Keep workloadIdentity.podRead.enabled: true for any Pod-bound caller that you register. AIControls reuses a successful lookup for runtimeControls.podOwnerCacheTTL (60 seconds by default).
Three cases are handled:
- A workload that shares another workload's ServiceAccount. A registration belongs to one workload, not to the ServiceAccount. If you copy a registered Deployment and leave it on the same ServiceAccount, the copy never inherits the registration. AIControls compares the workload that the calling Pod belongs to with the registered workload, treats a mismatch as unregistered, increments the
aicontrols_workload_identity_mismatch_totalmetric, and raises a notification that names both workloads. Give each workload its own ServiceAccount, then register the copy. - An unregistered Pod whose workload AIControls can't resolve. If the Kubernetes lookup for the calling Pod fails or exceeds the lookup budget, AIControls can't prove the workload is registered, so Enforce denies the call and the
aicontrols_unregistered_workload_lookup_failures_totalmetric counts it. - A registered workload whose Pod AIControls can't resolve. AIControls can't confirm that the Pod belongs to the registered workload, so it denies the call in every unregistered-workload mode, including Audit. The denial is a fail-closed decision with the reason
workload resolution unavailable, and a policy exception can't lift it. Theaicontrols_workload_registration_binding_unavailable_totalmetric counts it. AIControls doesn't cache a failed lookup, so the next call retries.
AIControls caches a resolved Pod for runtimeControls.podOwnerCacheTTL, shares one lookup between concurrent first calls from the same Pod, and waits briefly for lookup capacity before it gives up.
Change the mode in Settings → General → Default access policy → Unregistered workloads. In either mode, the Dashboard shows a callout for an unregistered workload that called the gateway in the past seven days: Blocked an unregistered agent when the gateway blocked the call, or An unregistered agent is calling the gateway when it allowed the call. Select Register it to open the intake wizard for that workload. The callout disappears once the workload has a registration.
To give the workload access:
- Find the workload. Select Register it in the Dashboard callout. Or open Security → Activity → Security and filter by the Inventory scope, or open Inventory → Workloads and filter by Discovered. The workload's drawer shows Why this workload is listed, for example "Called openai/gpt-4o through the gateway at 2026-10-06T12:00:00Z".
- Confirm it's yours. Check the namespace, the calling identity, and the model or tool it called. A workload you don't recognize is a shadow AI workload to investigate, not to register.
- Register it. In the security event, select Register workload. From the Workloads list, select Register anyway… in the row menu. Either opens the intake wizard, prefilled from what AIControls observed. Keep the default Blueprint and Learn first, then select Register on the Review step.
- Retry the call. The block lifts within one second of registering. With Learn first, the workload's calls are allowed and recorded in Audit, unless the default access policy holds or denies unmatched calls, and they become recommendations on the workload's AI access tab. Promote the grants it needs, then switch it to Enforce.
To change the mode, open Settings → General → Default access policy and choose Enforce or Audit under Unregistered workloads. On an upgraded installation, review the recorded events, register the workloads you expect, and then switch to Enforce.
Registration creates the workload record
Registering changes Registration from Discovered to Registered. Runtime independently reports Pending, Running, or Gone.
A build declaration that matches the running image can prepare controls, but it does not create a registration. Enforcement and Verification remain independently derived. See Workload Protection and Enable enforcement for a workload.
What happens next
- The registration is checked and stored. The response previews what each tier would expand to in that namespace, names any tier your platform team has not set values for yet — an unbound tier enforces nothing until they do — and states, per tier, what is enforced versus what is only observed (see what a tier can enforce).
- The next reconciliation matches registered behavior to what the workload is running and derives all four statuses.
- If controls are ready, Enforcement becomes Observing and builds an evidence window.
- An operator reviews that window and enables enforcement. Verification remains Not verified until the controls are confirmed active and holding.
- If you registered it with Learn first (Audit) or Trusted: enforce now, the workload's access already follows its Blueprint's Access Binding. Otherwise, bind its identity to give it access to MCP tools and LLM models. Enforcing the workload's access is separate from enabling enforcement. See Workload access.