Control what model calls can reach
For: tenant administrators and cost owners.
Set data rules before routing calls to a provider. Check provider state, requested addresses and model provenance.
Data rules
A tenant sets where its data may go:
oz0 model rules --region eu --no-retention
The gateway checks every call against the provider's region (eu allows eu and eu-north) and retention, and refuses one that breaks a rule with 403 oz0_data_rule and the reason. The refusal is recorded with its job, so the cost log counts it. Aliases skip the models the rules forbid. Local models on the tenant's own nodes pass every rule. A provider record without a region fails every region rule. For a tenant pinned to a region, rules that name no region allow only providers in its region; name the regions to allow others.
Rules that cannot be read (a row restored from elsewhere, or changed outside oz0 model rules) allow no provider: every call of the tenant to a provider is refused with 403 oz0_data_rule, which says so, until a tenant admin sets the rules again, and setting them counts as loosening (four eyes where they are on).
A tenant's own provider record, alias or mapping rule that cannot be read is not replaced by anything: its calls are refused, the shared record of the same ID does not stand in for it, and a job start that applies the tenant's mapping rules fails with the rule's ID. The edge logs each at error level.
A rule can also turn something on: oz0 model rules --hosted-web-tools lets providers run their hosted web tools for this tenant. It counts as loosening the rules.
A remote agent's session talks to its provider from the node, so before it starts or resumes, the node asks the edge whether the job may reach the provider's hosts: the tenant's data rules, its rules by level and its egress list for the job's level are checked as for a model call, and a session they refuse does not start, with the reason.
What a call may make the provider read or fetch
A model call's body can do more than ask. It can name something the provider keeps, it can name an address the provider fetches from or posts to, and it can turn on a tool the provider points at the internet. The gateway reads every call's body for these before it reserves anything, in every format and on every record, the free count of a request's tokens included. A refused call reaches no provider. It is recorded and audited as a refusal with the field's name, never with what the field held.
Stored provider objects
A provider knows accounts and workspaces, not tenants. Comparing keys proves nothing: two different keys can open one workspace. So a call that names a stored object gets 403 oz0_provider_state unless the record says the account is this tenant's alone.
That covers prior responses (previous_response_id), conversations, stored prompts, file IDs in any input, vector stores, containers, skills, stored agents and presets (agent_id, preset, a model named @preset/...), routes under a stored thread, assistant or conversation, cached content, tuned models, Google's file URIs and cloud storage objects, Vertex AI Search's data stores and engines.
It covers stored items too, however they are named: an item of a call's input that has an ID and does not carry what its type needs to stand alone is a reference to the stored one, such as a message without content, a generated image without its result, or reasoning without its encrypted content.
Send the content in the call itself. The agents and harnesses that ship with Orchestrator Zero do, and keep their conversations themselves: on the Responses format they ask for reasoning's encrypted content where the model has it, and leave out reasoning that came without it.
The way through is a cluster admin's word on a tenant's own record:
oz0 provider set mine --tenant acme --provider-state own
own asserts that the provider account behind this record as it stands now, and behind each member of its pool, serves this tenant alone: nobody else's key opens it. Orchestrator Zero cannot check that; it can only refuse the word when the keys' hashes already show another tenant on the same key.
A record every tenant sees cannot carry it. A tenant's admins can neither set it nor take it back, and while a record carries it only a cluster admin may change the record's key secret, address, sign-in or pool.
Each change is in the audit trail as provider.set. To use it with a built-in provider, give the tenant its own record of that ID. The gateway keeps no list of what a call made, so own opens all of the account's objects to all of the tenant's calls.
The word is about the account it was said of, so it can end without anyone taking it back:
- A new value in a secret the record reads its key from ends it, and so does a rollback, a delete or an export of that secret, on any role. For a secret in your own store that is when an edge reads the store's new version, once a
cache_ttl, and records it.
The gateway stops reading the word within a few seconds of the new value. Management then takes it off the record, within about a minute, and the audit trail says so asprovider.state_ended, bysystem:provider-state, with the record and the secret.
A rotation never waits for this and cannot fail on it. It is a moment to say the word again; set the key first and the word second. Calls that name no object do not notice. - When the keys' hashes later show another tenant on the same key, the gateway stops reading the word within a few seconds of the hash being recorded. The record still shows it, beside the finding, until a cluster admin takes it back or the other tenant's key changes.
An outside store that overwrites a value under the same version shows no change, and only the hash check above sees it.
If the secrets or the hashes cannot be read at all, the gateway does not read the word.
While management is away an edge goes by the last answers it has, for up to a day; a change the edge could not record in that time counts when it can.
Addresses a provider fetches
A field that makes the provider fetch from or post to an address is held to the tenant's egress list: image, document, video and audio URLs in any of the shapes OpenAI-compatible servers read (a url or uri beside a media type or under a file or document, input_audio as a URL), a file URL as a string or in an object, remote MCP servers.
A stored prompt's variables are read the same way, whatever they are called, also under own. An address that is not on the list, or one that URL parsers could read two ways, gets 403 oz0_provider_egress with the reason.
Inline content passes: data: URLs and base64. A job at a level with its own egress list is held to both lists. If the list cannot be read, the call gets 503 oz0_provider_egress_unavailable and can be tried again.
Hosted web tools
Some tools reach addresses nobody can name beforehand: web search, web fetch, Google's URL context, a search plugin, and any hosted tool the gateway has no entry for. Some models search with no tool in the call, and the gateway treats the families it knows the same way: a name with :online, names with -search-preview, -search-api or -deep-research, Groq's compound models and Perplexity's sonar models, on any of a router's variants (sonar:nitro).
All of these are off until a tenant admin turns them on with oz0 model rules --hosted-web-tools. A call that asks for one before that gets 403 oz0_provider_egress, whichever provider it is for.
The list of model families is a list: a family that appears later and searches by itself is the provider's, as below.
A job at a level with an egress list stays without them, since nothing can hold them to a list.
The Codex harness starts with its web search off, so its calls are not refused; a harness that asks for a search in a call of its own sees the refusal as a failed tool.
What stays the provider's is on the threat model. A local model server gets inline content only: it runs outside the job's sandbox, so an address it would fetch is refused on the node, and the refusal is recorded on the job like the gateway's.
Provenance and vetting
A model's digest tells a node it loaded the bytes it expected; it says nothing about where those bytes came from or whether anyone looked at them. An edited model (a fine-tune, a merged adapter, an abliteration) can answer normally until a trigger, then drive a shell to fetch a payload and send your secrets out. So the server records each model's provenance, and treats a model nobody has vetted with more care, without refusing it.
What is recorded, for each model the tenant's agents may run on and each model it vetted:
| Field | What it is |
|---|---|
| Reached through | A built-in provider (anthropic, openai), a shipped preset, an endpoint of the tenant's own, a local model server, or the runtime's stand-in test models |
| Publisher, source, base | Who published it, where it came from (a Hugging Face or registry reference) and the model it was edited from: what the provider says for a single-vendor provider, and what the admin who vetted it said |
| Endpoint | Where its calls go: the provider record's base URL, a pool's members, or node for a local model |
| Digest | A local model's sha256: as the nodes that serve it report it, and whether each node confirmed it against its pin or serves it with allow_unpinned |
| Vetting | Who vetted it, when, why and the decision it follows, and the digest or endpoint it was vetted for |
oz0 model provenance lists them, oz0 model provenance <provider>/<model> shows one, the console has them under Govern, Policy, Models, the inventory marks the unvetted ones, and an evidence bundle carries them.
Which models count as vetted. The server works it out from where a provider record's calls go, never from a flag on the record (oz0 provider set changes records without review, and --preset copies a preset into a record of your own):
| The model is reached through | Vetted |
|---|---|
| A built-in provider at the vendor's own address, or any record whose base URL is one of them | Yes |
| A record whose base URL is a fixed preset's (OpenRouter, Mistral, Groq, DeepSeek, Together, xAI, Google), under any ID | Yes |
A record whose base URL fits a preset that takes your region or resource: https://<resource>.openai.azure.com/openai, https://<resource>.services.ai.azure.com/anthropic, https://<region>-aiplatform.googleapis.com, https://bedrock-runtime.<region>.amazonaws.com | Yes |
| A pool | Only when every member is |
The runtime's test/... stand-ins, which run no weights | Yes |
Any other base URL: the vllm preset (a server of your own), a record cloned from a preset and pointed elsewhere, --shared --base-url to your own address | No, until vetted |
A local model (local/<model>) | No, until vetted |
The server's --llm-anthropic-url and --llm-openai-url point the built-in records elsewhere for the whole cluster, an operator's choice made on the server itself: the built-in records stay built-in.
Jobs on unvetted models
A job whose agent may run on a model the tenant has not vetted starts tainted, so the taint-default rule makes each call it decides that can send data out wait for a person, in the job and in the jobs it starts; and it runs on a node with sandbox tier 1 only, its start included.
Where the tenant has no node that can take it, it fails and says why and what to do: add a tier-1 node, opt a node out, or vet the model; while tier-1 nodes are busy, it waits like any job.
It is never refused for its model: local models are part of the product, and a tenant that wants to refuse one writes a rule for it.
The job's page names the model under Unvetted model, its labels name it as the reason it is tainted, and its start's audit entry says unvetted_model.
The edge decides it before the job starts, because the node resolves the model only once the job has a home. It checks the agent's model or alias through the tenant's mapping rules, fallbacks and cascade, its best-of candidate models, and the judges its outcome check or best-of asks.
It also checks the agents and flows the job may reach as they stand at its start: delegates, reviewers, best-of agents, colleagues, the flows of its flow tools and a flow's step agents. A step whose agent is an expression counts every agent it may pick.
One unvetted model among them is enough, and every job the marked job starts carries its taint and its need of tier 1, so the delegate or the step that runs the unvetted model runs on tier 1 too.
A routine's schedule carries the mark in each fire it starts.
Vet a model, as a tenant admin, with a reason:
oz0 model vet local/qwen3-coder --reason "model card, licence and release notes reviewed" --decision-ref CAB-7 \
--publisher Qwen --source hf:Qwen/Qwen3-Coder-8B --base Qwen/Qwen3-8B
oz0 model provenance local/qwen3-coder
oz0 model unvet local/qwen3-coder --reason "the publisher withdrew this release"
Model local/qwen3-coder: vetted
why: vetted at sha256:6f2c...
reached through: local
publisher: Qwen
source: hf:Qwen/Qwen3-Coder-8B
endpoint: node
base model: Qwen/Qwen3-8B
digest: sha256:6f2c... (confirmed against each node's pin; 2 node(s))
vetted by user:ana at 2026-10-21 08:14 UTC: model card, licence and release notes reviewed
decision: CAB-7
Vetting is a reviewed change: it loosens what the model's jobs need, so it takes a reason and, where the tenant has four eyes on, another admin's approval; removing a vetting tightens and applies at once.
A local model's vetting holds for the digest its nodes report when it is vetted (they must agree, or name one with --digest), and lapses while a node serves other weights under the name.
When no node reports a digest, name one with --digest, or vet the model by its name with --by-name, which covers any weights a node serves under it and which the change and the audit show.
An endpoint's vetting holds while its record calls the address it called when the vetting was asked for.
Under four eyes the request keeps that digest and that address, and an approval applies the vetting to them only: a provider record pointed elsewhere in the meantime (oz0 provider set is not a reviewed change) makes the approval fail, and the vetting is asked for again.
Both are audited (provider.model_vetted, provider.model_unvetted). A model behind a built-in provider or a preset needs no vetting, and the command says so.
A vetting is a person's judgement, not a scan of the weights: it records who looked and why, and never says a model is safe. Nothing in the platform detects a backdoor in a model's weights, vetted or not; the controls outside the model (the sandbox, the egress list, taint and approvals) are what stand between a model's answers and your systems.
Vetting scope and timing
Vetting is decided when a job starts. Adding or removing a vetting changes later jobs and schedule fires, not jobs already running.
An agent or flow that names more than 50 reachable agents exceeds the registry's retained list. The edge treats its jobs as potentially using an unvetted model and provides the tenant's pools with tier-1 queues, rather than assume the omitted agents are vetted.
The start check does not weigh a sampled job's separate one-call judge or a tool that calls a model through the node's model proxy. An Ask job using an unvetted alias starts tainted but keeps Ask's routing because it has no shell.
The edge marks a start before sealing data-subject input. It refuses input sealed without the required mark. These controls enforce the recorded vetting decision; they do not inspect model weights.