---
title: "Tenants"
description: "Separate teams or customers in one cluster."
canonical_url: "https://docs.oz0.ai/admin/tenants"
---
# Tenants

> Separate teams or customers in one cluster.

For: cluster and tenant administrators.

Use tenants to separate teams or customers. Assign roles and content access according to the work each person must perform.

A tenant is an isolated team or customer in a cluster. Each tenant has its own:

- Nodes. A node belongs to one tenant, chosen by its join token or by the operator who accepts it.
- Plugins. Installing a plugin installs it on that tenant's nodes only. Agent names are unique per tenant.
- Secrets. A secret sealed for one tenant cannot be opened for another.
- Temporal namespace, with the same name as the tenant, on one of the cluster's [Temporal backends](https://docs.oz0.ai/fleet/temporal-backends). Jobs of one tenant never share a namespace with another's.
- Usage. Cost is recorded per tenant, under its [budget](https://docs.oz0.ai/build/parts/contracts-and-limits/budgets#budgets).
- Rules. Its [tool policy](https://docs.oz0.ai/govern/tool-policy), the hosts its [sandboxes](https://docs.oz0.ai/concepts/security/sandbox-tiers#sandboxes) may reach, its [data rules](https://docs.oz0.ai/spend/models-and-keys) and its [retention](https://docs.oz0.ai/govern/audit-trail/retention#retention), and the [AI label](https://docs.oz0.ai/govern/ai-labels) on what its agents write for people.

A call that names an object of a tenant by its ID (a node, an API key, a join token, a rollout, a kill switch, a budget request, a receipt) gives a caller who may neither read that tenant nor act in it the answer it gives for an ID that names nothing: it says neither that the object exists nor which tenant holds it.

For a node, a key, a join token, a rollout or a kill switch, a member of the tenant who lacks the permission is told what they lack; a budget request and a receipt answer as not found to anyone who may not act on them, members of the tenant too.

Every cluster starts with the tenant `default`. Create more with:

```bash [Terminal]
oz0 tenant create acme --display-name "Acme Inc."
oz0 tenant list
```

Most commands take `--tenant`, which defaults to `default`. Servers register a Temporal namespace for every tenant at start and every 30 seconds after.

The rest of a tenant's settings are `tenant` subcommands too:

```bash [Terminal]
oz0 tenant budget acme 500                            # US dollars a calendar month
oz0 tenant egress acme api.github.com '*.pypi.org'    # hosts its sandboxes may reach
oz0 tenant review-widening acme second_admin          # who approves a widening of them
oz0 tenant tool-policy show --tenant acme              # what its tools may do
oz0 tenant ai-label show --tenant acme                # the AI label on what its agents write
oz0 tenant chat-titles acme model                     # chats titled by the agent's model, not the first message
oz0 tenant chat-read-notices acme on                  # owners see who read their chat through the audited access
oz0 tenant backends                                   # which Temporal backend each tenant is on
oz0 tenant move acme cloud                             # move it to another backend
oz0 tenant removed-account-keys acme finding          # keep the keys of people who leave, listed (default: revoke)
```

Widening what a tenant's sandboxes may reach is a reviewed change: a new egress entry, a plugin whose egress the tenant does not permit yet, or a credential binding that allows more waits as a change request until a tenant admin approves the delta they saw (`oz0 change`, Approvals to Changes).

The tenant's `review-widening` is `confirm` by default, under which the admin who asked may approve after seeing the delta, or `second_admin`, under which another admin must: regulated tenants should use it. Relaxing it back takes an owner. A new entry that reaches a host serving files anyone can publish (a code host's raw files, a package registry, an object store) says so on its finding, as a reason to look; it is approved like any other. See [Reviewed changes](https://docs.oz0.ai/govern/reviewed-changes).

A tenant's people, API keys and services can be grouped in [teams](https://docs.oz0.ai/spend/budgets-and-quotas), each with a quota of its own under the tenant's budget.

A conversation a person starts is their [chat](https://docs.oz0.ai/agents/conversations/access-and-history#chats-yours-private-by-default), private to them and those they share it with. Tenant admins change two settings, each audited:

- `chat-titles` defaults to `first-message`, using the chat's first words as its title. With `model`, the agent's own model writes it in the first job: one small metered request, marked as written by AI. The edge never asks a model on its own.
- `chat-read-notices` is on by default for now. The owner sees who read the chat through audited content access, when and why.

Accounts have a role in each tenant, and the permission to see what the tenant's jobs read and wrote is separate from the role. See [Roles and permissions](https://docs.oz0.ai/admin/tenants#roles-and-permissions) below and [Console](https://docs.oz0.ai/getting-started/web-ui/accounts#accounts-and-roles). Operator certificates, which the CLI uses, manage every tenant.

## Roles and permissions

A role is a named set of permissions, and every API call needs one permission in the tenant it acts in. `oz0 roles` lists the roles in force; `oz0 roles --permissions` lists every permission with its *level*, the least of reader, operator, admin and owner that holds it.

<table>
<thead>
  <tr>
    <th>
      Built-in role
    </th>
    
    <th>
      What it may do
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        reader
      </code>
    </td>
    
    <td>
      See the tenant: nodes, agents, plugins, jobs and their steps' shape, cost, findings, usage, the inventory and governance settings
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        operator
      </code>
    </td>
    
    <td>
      Also start and cancel jobs, decide approvals and answer questions, fire routines, call tools
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        admin
      </code>
    </td>
    
    <td>
      Also change the tenant: nodes, plugins, secrets, keys, the budget and governance; read its audit log and export evidence
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        owner
      </code>
    </td>
    
    <td>
      Also decide who is a member of the tenant, with which roles
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auditor
      </code>
    </td>
    
    <td>
      What a reader sees, and the audit log, evidence exports, retention and held histories, settings and secrets' names; changes nothing
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        privacy-officer
      </code>
    </td>
    
    <td>
      What a reader sees, and finding and erasing data subjects, retention and legal holds read, legal holds placed, <a href="https://docs.oz0.ai/govern/incidents">
        incidents
      </a>
      
       kept and the <a href="https://docs.oz0.ai/admin/tenant-export">
        tenant exported
      </a>
      
      ; starts no job and installs nothing
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        agent-user
      </code>
    </td>
    
    <td>
      Use the agents the membership's agent grant names in the chat drawer: see its own chats and jobs, cancel them, answer their questions, and decide an approval only where its deciders name the person
    </td>
  </tr>
</tbody>
</table>

The four ordered roles each hold what the one before holds, exactly as before roles were sets. The permissions, by level:

- reader: `tenant.read`, `jobs.read` (with `jobs.read.own`), `feeds.read`, `chats.use`, `usage.read`, `inventory.read`, `governance.read`, `findings.read`, `budget.request`.
- operator: `jobs.start` (with `jobs.start.granted`), `jobs.cancel` (with `jobs.cancel.own`), `jobs.manage`, `approvals.decide` (with `approvals.decide.named`), `questions.answer` (with `questions.answer.own`), `routines.run`, `tools.call`, `settings.read`, `candidates.write`, `evals.keep`, `payloads.encode`, `comparisons.start`.
- admin: `audit.read`, `evidence.export`, `retention.read`, `retention.write`, `holds.place`, `subjects.find`, `subjects.erase`, `secrets.read`, `secrets.write`, `settings.write`, `plugins.install`, `nodes.manage`, `keys.manage`, `connections.manage`, `budget.manage`, `teams.manage`, `models.manage`, `governance.write`, `changes.review`, `inventory.write`, `notifications.manage`, `routines.manage`, `channels.manage`, `workspace.write`, `evals.manage`, `usage.export`, `setup.check`, `incidents.read`, `incidents.manage` (with `incidents.read`), `tenant.export`.
- owner: `members.manage`, `signin.manage`, `governance.relax`.
- `content.read` is the membership's content permission, never part of a role.

A membership may hold up to four roles, separated by commas, and gets what any of them allows:

```bash
oz0 user grant ada --tenant acme --role operator                 # the content permission by default
oz0 user grant ida --tenant acme --role auditor                  # no content unless --content
oz0 user grant cleo --tenant acme --role agent-user --agents helpdesk,billing/*,tag:hr   # content by default: her own chats
oz0 tenant role-rules acme                                       # separation of duties
```

Agent grants. `--agents` names agents, every agent of a plugin (`<plugin>/*`), or every agent an agent definition tags (`tag:<tag>`, the agent definition's `tags:`). An agent user starts only those, and nothing without a grant; a grant on any other role narrows what it starts. The chat drawer's picker, Ask's hand-overs and `StartJob` all apply it.

An agent user sees only the jobs it started: another person's job, its approval's arguments, a turn continued from it and a conversation it did not start answer *not found*. Its membership has the content permission by default, since it reaches only its own chats and jobs; with `--content=false` it starts agents but cannot read their answers.

Separation of duties. A membership never holds `auditor` together with a role that changes the tenant (any admin-level or owner-level permission that does more than read), so an auditor stays independent of what it audits. A tenant's owners may allow it with `oz0 tenant role-rules <tenant> --auditor-with-changes --reason ...`, a governance change that takes a reason and, with [four eyes](https://docs.oz0.ai/govern/reviewed-changes#four-eyes-for-governance-changes), a second admin. A sign-in whose groups would give both keeps `auditor` alone; the audit log says so when the membership changes (`membership.roles.conflict`), and `oz0 doctor` lists the people kept at auditor (`roles.sod_kept`).

Role sets. A server started with `--role-sets-file <path>` or `--role-sets-command <command>` adds the roles of a role set to the built-in ones:

```yaml
version: oz0-roles/v1
roles:
  - name: support
    description: Reads every job and cancels them
    permissions: [tenant.read, jobs.read, jobs.cancel, feeds.read, chats.use]
```

A role names permissions from the list above; it may not redefine a built-in role or name `content.read`.

The server reads the file or runs the command (without a shell, with `--role-sets-timeout`, 10 s by default, and a small environment of its own: give it what it needs as arguments) at start and every `--role-sets-refresh` (5 minutes), stores the last valid set, and every edge takes it from the registry. If the command fails, times out or prints something invalid, the last valid set stays in force and `oz0 doctor` says so (`roles.source_failed`).

A role that leaves the set, or a set that is taken away, leaves memberships naming it with the strongest of reader, operator, admin and owner whose permissions it held entirely (none if it held less than a reader's): never more than before.

Each change of the set is in the audit log (`roles.set.changed`), and `oz0 doctor` lists the memberships and group rules that still name a retired role (`roles.fallback`); a retired role nobody names any more is dropped.

In a cluster with several management servers, give every one the same `--role-sets-file` or `--role-sets-command`: a server started without it reads that as the set being taken away, and retires the custom roles until another server's next reading.

Nobody gives a role that holds more than they hold themselves, and a role with `members.manage` below the owner's also cannot change or remove a member who holds a permission it does not, give the content permission it lacks, or give an agent grant wider than its own.

The same holds for API keys: below the owner, a role with `keys.manage` makes no key whose role (reader, operator or admin) holds a permission it does not, and no key with content unless it has content itself. A tenant keeps its last owner: only a cluster admin removes or demotes it. The check runs in the transaction that changes the membership, with the tenant's members locked, so two owners demoting each other at once, through two management servers too, cannot both pass it.

Apps call the [runtime API](https://docs.oz0.ai/build/apps/runtime-api/authentication#authentication) with a tenant's API keys (`oz0 apikey create`), each with one of the same roles; a key works in its own tenant and nowhere else, and a key [scoped](https://docs.oz0.ai/build/apps/runtime-api/authentication#keys-scoped-to-agents-and-flows) with `--scope agent:<name>`, `flow:<name>` or `conversation:own` works only on those resources.

What a key creates is labelled with it (`created_via`). A key belongs to the tenant and records who made it. When that person's account is [removed](https://docs.oz0.ai/admin/sign-in/provisioning#accounts-from-a-directory), the tenant revokes the keys they made, unless its admins chose to keep them working as a finding with `tenant removed-account-keys`.

A tenant's owners make [admin keys](https://docs.oz0.ai/build/apps/runtime-api/authentication#admin-keys) for tools that administer it, with a set of permissions they hold (and never more than they still hold) and an expiry of at most 90 days, and register [apps that act as their people](https://docs.oz0.ai/build/apps/runtime-api/authentication#apps-that-act-as-their-people), each member narrowed to what the app may do.


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
