Govern

Taint and labels

How a job's labels track what it read: outside text taints the job tree, a tainted job asks before tools that can send data out, a quarantine child reads what its parent should not, and label policies withhold, narrow egress, pick models and wall off compartments by label.

For: tenant admins and governance reviewers.

A job carries the trust and data labels of what it reads. Those labels follow results and child jobs so later calls can be checked against the tenant's rules.

Every job carries a label set: what it has read, as far as the platform can tell. The label set has two parts:

  • Trust: trusted, or untrusted once the job read text from outside, with up to 20 reasons (where it came from, which function and which call; never what the text said).
  • Confidentiality: a level from your tenant's ordered list (lowest first) and a set of compartments, which tool results declare.

The labels are joined outside the model, in the job's workflow, from what the job's history recorded. They only grow: untrusted wins, the higher level wins, compartments unite. Tool-policy rules read them, and the default rule asks a person before a tainted job calls a tool that can send data out.

Taint limits prompt injection; it does not stop it. It makes the platform ask before text a job read can carry data out, and it never lets text from outside hide in a sub-agent. It does not stop a model from misusing what it read before any taint, a tainted summary from misleading the person who reads it, or a plugin from labelling its results wrongly.

What taints a job

A job is untrusted from its start when an outside event started it:

StartReason (source)
A webhook's or an event's delivery, a routine fire whose trigger carried a payloadevent
A chat messagechannel
An A2A calla2a
A call through the edge's MCP servermcp_caller
A turn an app started through the AG-UI routeagui
Any other job an outside event started (the sandboxes' outside)outside_start

Whoever starts it, a job whose agent may run on a model the tenant has not vetted (a local model, or one behind an endpoint of the tenant's own) is untrusted from its start too, with the reason unvetted_model and the model as its tool: an unvetted model's answers are treated as text from outside. Such a job also runs on sandbox tier 1 only.

A job a person or an app starts with StartJob, and a scheduled routine fire without a payload, start trusted. So do the flows and routine fires of the built-in inventory source: the platform writes those events about its own records, and they carry no text from outside. Such a job still runs where outside-started jobs run, and what its tools read taints it as always: a directory's answer to a registration plugin's call is an open-world result like any other, so the next outbound call after it asks.

During a run, these join untrusted labels into the job:

What the job readReason
The result of an open-world function: one whose annotations do not say openWorldHint: false, and every remote server's, whatever its read-only hintopen_world
A result that declares itself untrusted, or whose tool's manifest says labels: {untrusted: true}plugin
An event oz.wait_for_event returnedwait_for_event
What a routine's target answered to an output applied nowroutine_answer
A harness session, or a remote agent's session at its provider (its own tools, such as a shell or a web fetch, run inside it and their results never reach the workflow, so the session taints its job from its start)harness
A skill the agent read with oz.skill from an imported plugin or from another plugin than its own, a tool's manual of another plugin included, unless that plugin is a verified connectorskill
A tainted child's result, a tainted colleague's answer, a board note or a file a tainted job wrotechild, colleague, board, file
A failed flow tool's or Ask delegation's reason, which the model reads and which can hold the child's own wordschild

A tenant may vouch for an open-world function whose results it trusts (its own documentation search, say) with oz0 tenant labels --trusted. A plugin cannot make its own open-world results trusted.

Levels and compartments

A tool result declares its labels in its _meta under oz0/labels (building tools). A level is a name from your tenant's list; compartments are names such as matters or projects. An unlabelled result of a tool whose manifest declares a label source (labels: {source: drive}) gets the tenant's default level, never the lowest. A result that declares a level your list does not hold, or malformed compartments, is withheld: the model reads an error, never the content.

Terminal
oz0 tenant labels default
oz0 tenant labels default --levels public,internal,confidential,restricted --default-level internal \
  --reason "data classes of policy 4.2"

Without a list of its own a tenant has the built-in one: public, internal, confidential, restricted, default internal. Emptying the list, dropping or moving down a level, lowering the default level or vouching for a function loosens what labels protect: it needs a reason and, where four eyes is on, another admin. The console has the same form under Govern › Privacy › Labels, beside the label policies.

Labels and compartments are engineering controls. Rules on them decide what a job may do; they are not a legal or professional-conduct barrier.

How labels travel

PathWhat happens
A child job (oz.delegate, a flow tool's flow, best-of candidates and reviewers, Ask's delegations)Details
An MCP or A2A call that names the job it came fromits job holds the origin's compartments and level from its start: a call of the same person or key continues the origin's envelope, and one of another caller gets an envelope of its own that still carries the origin's labels
oz.delegate's datavalues passed beside the task, shown to the child's model in a <delegated_data> block marked with their trust only (no level or compartment reaches a model provider), never mixed into the task; the child's job carries the labels; every best-of candidate and a harness or remote agent's session get it too
A colleague's question and answerthe question carries the asker's labels into the colleague; the answer carries the colleague's back
The boarda note keeps its writer's labels; reading joins them
Files between toolsa file keeps every writer's labels; a call given the file joins them
An outcome check's judge that reads the job's files (outcome.files: read)its model call carries the job's labels and the files'; the labels of the files it was shown join the job's before its reasons reach the agent
A conversation's next turn, Ask's hand-overstarts with the labels of the turn it continues: those its runtime kept and those it was started holding, so a turn that ended before a node ran it passes on its conversation's compartments; a turn whose labels cannot be read is not continued; a fork from before the taint starts clean
Continue-as-newthe next run keeps the labels, and holds what the job was started holding: the edge copies it from the run's start into the next run's, whatever the runtime's memo says, and an edge that has not seen the run's start holds the continue-as-new (its task is offered again) until one that has
A job run again (oz0 job retry)starts with the old job's compartments and level; a job whose labels do not read does not run again
A comparison's run of a recent job, a rollout's shadow runholds its job's compartments and level from its start; what its calls read joins its labels as in a real run, so its floors ask where the real run's would (nobody is asked in a shadow run, so such a call is not made)
A sampled job's judge jobholds the judged job's compartments and level from its start, as a comparison's run does; its envelope carries them, so the rules by level hold the judge's model call as they held the job's; a job whose labels do not read is not judged
A governance replay's continuationmade only from a job that holds no compartment and the lowest level: a job that does is not continued
A kept eval case, a draft flow saved from a jobmade only from a job that holds no compartment and the lowest level: they keep what the job said where no wall reaches

Labels on child jobs

starts with its parent's labels; its result's labels join the parent's. It holds at least the compartments and level its parent was started holding from its own start, before a node runs it: the edge copies them from the parent's start into the child's, and an edge that has not seen the parent's start holds the child's start (its parent's task is offered again) until one that has.

Only the edge writes what a job was started holding: a value a node's runtime puts into a child's start, a continue-as-new or its job's memo is dropped.

The child's authority envelope carries the join of its parent's labels and its own, so the rules by level at the gateway hold for every descendant of a job that started holding a level, whatever the child's start says; the offline verifier reports a hop whose labels hold less than its parent's

The job's page and oz0 job get show the labels; the trace marks the step whose result tainted the job.

Labels that cannot be read are read as strictly as they can be. Where the server and the edges read labels that do not validate, or none where a call, a job's start, a file, a record or a result should carry them (the edge's checks, file and record labels, traces and replays), they count as untrusted, at the highest level and holding every compartment, never as the lowest.

On a node, a child's or a colleague's answer whose labels do not validate counts as untrusted, and one without the stamp every runtime with labels writes counts as untrusted at no less than the level and compartments it names.

The offline verifier holds a call recorded with no labels to the lowest set, so any label proven before it counts as a difference.

A trust word that is neither trusted nor untrusted is untrusted; a default level your list does not hold is its highest level.

Previous section links

clearance per agent: Read the section.

egress by level: Read the section.

label policies: Read the section.

label sources that do not label: Read the section.

models by level: Read the section.

rules on labels: Read the section.

the quarantine child: Read the section.

the taint default rule: Read the section.

walls: Read the section.

what is fixed and what is not: Read the section.

where the childs calls run: Read the section.

Copyright © 2026