---
title: "Data, evidence and mail"
description: "Look up the fields for data, evidence and mail."
canonical_url: "https://docs.oz0.ai/reference/plugin-manifest/data"
---
# Data, evidence and mail

> Look up the fields for data, evidence and mail.

For: Plugin authors looking up manifest fields.

Look up the fields for data, evidence and mail.

## `provides`

A record of the core that the plugin's tools fill beyond their own calls. Each kind is a permission: an install of a version that declares one the tenant has not granted is refused until a tenant admin accepts it with `oz0 plugin install ... --accept-provides <kind>` (audited `plugin.provides.granted`). The grant stays across versions, `oz0 plugin list` shows it, `oz0 plugin revoke-provides <plugin> <kind>` takes it back, and removing the plugin drops it. `oz0 dev` grants the working copy's in its dev tenant.

```yaml [oz0-plugin.yaml]
provides: [outside-spend]
```

The one kind there is:

- **`outside-spend`**: spend that belongs to no job, such as a model provider's admin cost report, shown as [outside spend](https://docs.oz0.ai/spend/cost-and-usage/accounting#outside-spend). A tool returns it in its MCP result's `_meta` under `oz0/outside-spend`, as a list of at most 500 rows.<br />

Each row contains:
  - `day`: a UTC day, `YYYY-MM-DD`, from the last three years up to today.
  - `provider`: a short lowercase name such as `anthropic`.
  - `amount_micros`: 0 to 10^13 micro-units of the currency.
  - `currency`: an ISO 4217 code such as `USD`.
  - `key`: at most 200 characters from `A-Z a-z 0-9 . _ : / @ + = -`, unique per row. Repeating the key changes the row instead of adding one.<br />

Optional fields are `account` (the provider's workspace or project, shown in no view), `team` (an OZ team's name), `model` (or cost line), `input_tokens` and `output_tokens`.<br />

The runtime takes the rows out of the result, so they never reach the model, the job's output or its history, and the edge records them only for a plugin whose installed version declares `outside-spend` and whose tenant granted it. The rows never count toward a job, a budget or a quota, and at most 5,000 rows are kept per plugin and day.<br />

The tool's text gets one line saying how many rows were added, changed and already there; a refusal makes the call an error.

```python [server.py]
row = {
    "day": "2026-10-08",
    "provider": "anthropic",
    "account": "wrkspc_01",
    "team": "research",
    "model": "claude-opus-5",
    "amount_micros": 2500000,
    "currency": "USD",
    "key": "anthropic/wrkspc_01/2026-10-08/opus",
}
result = CallToolResult(content=[TextContent(type="text", text="1 day read")], _meta={"oz0/outside-spend": [row]})
```

Nothing in a row may name a person: sum people's figures to teams in the plugin, before the rows leave it.

## `reads`

A record of the core that the plugin's functions read beyond their own calls. Each kind is a permission, granted as [`provides`](https://docs.oz0.ai/reference/plugin-manifest/data#provides) is: an install of a version that declares one the tenant has not granted is refused until a tenant admin accepts it with `oz0 plugin install ... --accept-reads <kind>` (audited `plugin.reads.granted`).

The grant stays across versions, `oz0 plugin list` shows it, `oz0 plugin revoke-reads <plugin> <kind>` takes it back at once (audited `plugin.reads.revoked`), and removing the plugin drops it. `oz0 dev` grants the working copy's in its dev tenant. Nothing a plugin reads this way can be written back.

```yaml [oz0-plugin.yaml]
reads: [inventory]
```

The kinds:

- **`inventory`**: the tenant's agents and flows as the [inventory](https://docs.oz0.ai/govern/inventory) records them. Each call of one of the plugin's own functions gets them in its MCP request's `_meta` under `oz0/inventory`: `tenant`, `at` (when the edge read them) and `agents`, each with `name`, `flow` (true for a flow), `plugin`, `version` (its plugin's installed version), `description`, `owner_team`, `owner_person` (`user:<name>`), `state` (`active` or `retired`), `retired_reason` and `retired_at`, `risk_class`, `purpose`, and `in_catalog` (false once its plugin was removed and the inventory still keeps it).<br />

Nothing else is in it: no models, regions, kinds of data, eval results, jobs or members. The runtime asks the edge for it before the call, with its own token, and the edge answers only for a plugin the calling node runs, whose installed version declares `inventory` and whose tenant granted it; the inventory goes to the call alone, never to the model, the job's output or its history.<br />

A call that gets none is not made, and its caller hears why. The edge audits a plugin's reads once per ten minutes (`inventory.read`, with how many reads the ten minutes before had). The plugin's routines and flows may also fire on the inventory's changes: the built-in event source [`inventory`](https://docs.oz0.ai/build/parts/event-sources/platform-events#the-inventorys-changes).

```python [server.py]
@server.tool()
def sync(name: str, ctx: Context) -> str:
    inventory = ctx.request_context.meta["oz0/inventory"]
    agent = next((a for a in inventory["agents"] if a["name"] == name), None)
    ...
```

- **`results`**: what the jobs of other plugins of the tenant came to (their findings, failing and kept eval cases, refused approvals with their reasons, error groups, cost per run and scorecards), which the plugin's agents read with the built-in tool [`oz.evidence`](https://docs.oz0.ai/improve/evidence-for-agents). Unlike the other kinds it is granted **for named target plugins**, by a person who holds `content.read`, never a key: `oz0 plugin install ... --accept-reads results --reads-target <plugin>` (repeatable; the install screen asks which when none is named, and none means it reads nothing), then `oz0 plugin reads grant|revoke <plugin> results --target <plugin>`.<br />

A target added is a governance change, with a reason and four eyes when the tenant has them on; each grant and revocation is audited with its target. The edge answers every read with the permissions the granting person holds at that moment, and the plugin's routines and flows may fire on the built-in event source [`results`](https://docs.oz0.ai/build/parts/event-sources/platform-events#the-results-of-granted-plugins).<br />

See [Evidence for agents](https://docs.oz0.ai/improve/evidence-for-agents).

```yaml [oz0-plugin.yaml]
reads: [results]
```

## `mail`

The plugin's functions send mail through the edge. A permission like [`reads`](https://docs.oz0.ai/reference/plugin-manifest/data#reads): an install of a version that declares it is refused until a tenant admin accepts it with `oz0 plugin install ... --accept-reads mail`, and `oz0 plugin revoke-reads <plugin> mail` takes it back at once.

```yaml [oz0-plugin.yaml]
mail:
  connection: email-graph   # optional
connections:
  - name: email-graph
    tools: [mytool]
    # ...
    scopes:
      - functions: [mytool.mail_token]
```

Each call of a function of the plugin's own (local) tools gets, in its `_meta` under `oz0/mail`, the address of a listener of the node's runtime and a ticket good for that call only. The function posts the message there as JSON (`op` `check`, `send`, `prepare` or `sent`, and `message` with `to`, `cc`, `subject`, `text`, `html` and `attachments`); the runtime adds the plugin, the tool, the function, the job, the call's idempotency key and binding, and lets only the bytes of the files the call was given be attached.

The edge checks the recipients against the tenant's [mail domains](https://docs.oz0.ai/build/plugins/verified/email#the-tenants-mail-domains), adds the [AI label](https://docs.oz0.ai/govern/ai-labels) and `Auto-Submitted`, refuses a send from a function whose listing is read-only, and either sends the message through the operator's relay (`send`) or hands it back rendered as MIME (`prepare`), for a mail service the plugin sends it through itself, followed by `sent` with the receipt `prepare` gave.

Every message sent is audited as `mail.sent`.

`connection` names one of the plugin's connections whose token the edge mints with a message `prepare` rendered, and nowhere else: its scope names `<tool>.mail_token`, a function no server may list, so no call fetches the token before it starts and a tenant that sends through the relay needs no such connection. [ai.oz0/email](https://docs.oz0.ai/build/plugins/verified/email) is the plugin that uses it.

## Card keywords in schemas

Output schemas (an agent's or a flow's `output_schema`, and the `outputSchema` a tool's MCP server lists) may carry two keywords that shape how a matching result is drawn as a card, and input schemas the first:

- **`x-oz0-order`**: a list of property names, shown first: a table's columns, key-values' keys, and the fields of an edit form or a typed question. Lint warns about a name that is not a property.
- **`x-oz0-diff`**: `{before: <property>, after: <property>}` at an object schema's root: a result holding both is drawn as a diff (lines for two texts, keys for two objects). Lint refuses one that does not name two different properties of that schema.

See [results drawn as cards](https://docs.oz0.ai/build/parts/contracts-and-limits#results-drawn-as-cards).

[Back to oz0-plugin.yaml](https://docs.oz0.ai/reference/plugin-manifest).


## Sitemap

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