---
title: "Evidence for agents"
description: "Let an agent read another plugin's job outcomes and install details under a grant, with the granting person's permissions checked at every read."
canonical_url: "https://docs.oz0.ai/improve/evidence-for-agents"
---
# Evidence for agents

> Let an agent read another plugin's job outcomes and install details under a grant, with the granting person's permissions checked at every read.

For: Plugin authors giving agents access to evidence.

Grant a plugin `reads: [results]` when its agents need evidence to propose changes. The built-in `oz.evidence` tool reads results from the plugins named in the grant, including failures, refused approvals and cost. It writes nothing.

Every read checks the granting person's current permissions. A grant cannot give the agent access that person no longer holds.

## What it reads

For one target plugin (and optionally one of its agents or flows), over the last 30 days by default and at most 90:

<table>
<thead>
  <tr>
    <th>
      Kind
    </th>
    
    <th>
      What
    </th>
    
    <th>
      Needs, of the granting person
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        findings
      </code>
    </td>
    
    <td>
      The target's <a href="https://docs.oz0.ai/improve/errors-and-insights">
        findings
      </a>
      
      , open and resolved: kind, agent, state, message
    </td>
    
    <td>
      <code>
        findings.read
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        failure_classes
      </code>
    </td>
    
    <td>
      Not available in this release
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        failure_kinds
      </code>
    </td>
    
    <td>
      How the target's agents' jobs failed: per agent, each <a href="https://docs.oz0.ai/build/parts/contracts-and-limits/failures#failure-kinds">
        failure kind
      </a>
      
       its jobs ended with, how many, and whether the agent declares the kind or it is one of the platform's own
    </td>
    
    <td>
      <code>
        findings.read
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        evals
      </code>
    </td>
    
    <td>
      Failing cases of the target's <a href="https://docs.oz0.ai/improve/evals">
        eval
      </a>
      
       runs: eval, case, run, version, verdict; with content, the case's input and expected output
    </td>
    
    <td>
      <code>
        usage.read
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        kept_cases
      </code>
    </td>
    
    <td>
      Cases kept from the target's jobs: eval, case, job, label, failures and runs; with content, the input and expected output
    </td>
    
    <td>
      <code>
        findings.read
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        refused_approvals
      </code>
    </td>
    
    <td>
      Approvals people refused on the target's agents' jobs: job, agent, version, approval, when; with content, the reason the person gave
    </td>
    
    <td>
      <code>
        jobs.read
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        errors
      </code>
    </td>
    
    <td>
      Error groups of the target's agents: fingerprint, step, version, count, jobs, first and last seen, and the failure's kind in a fixed sentence; on a group an older edge kept with its words, those words and an example while the granting person may read them
    </td>
    
    <td>
      <code>
        findings.read
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cost
      </code>
    </td>
    
    <td>
      Cost per run per agent and version, and per team the calls were metered to
    </td>
    
    <td>
      <code>
        usage.read
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        scorecards
      </code>
    </td>
    
    <td>
      Per agent and version: runs, successes, success rate, cost, cost per success; and the figures of people's decisions on its approvals
    </td>
    
    <td>
      <code>
        usage.read
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        installed
      </code>
    </td>
    
    <td>
      Where the target is installed from and whose it is: see <a href="#where-a-target-is-installed-from">
        below
      </a>
    </td>
    
    <td>
      <code>
        tenant.read
      </code>
    </td>
  </tr>
</tbody>
</table>

**Content** is what jobs read and wrote: eval inputs and expected outputs, the reasons people gave, error examples. It is in the answer only while the granting person holds `content.read`. A kind whose permission they lack is left out with the permission it needs. A group's words name no job, so a person a [wall](https://docs.oz0.ai/govern/taint-and-labels) applies to gets the group's facts without them, whatever they hold.

### Where a target is installed from

The `installed` kind answers, for the target, one row of the platform's own record of its install:

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      What
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        version
      </code>
      
      , <code>
        commit
      </code>
    </td>
    
    <td>
      The installed version and the commit (or digest) it is at
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        source_kind
      </code>
      
      , <code>
        source
      </code>
    </td>
    
    <td>
      <code>
        git
      </code>
      
       and its URL with any user, password or token taken out; <code>
        hub
      </code>
      
       and its listing (<code>
        hub:&lt;publisher>/&lt;name>
      </code>
      
      ); <code>
        oci
      </code>
      
       and its reference; <code>
        bundle
      </code>
      
       and "uploaded bundle"; or <code>
        workspace
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        path
      </code>
      
      , <code>
        ref
      </code>
    </td>
    
    <td>
      Its folder in the repository and the ref it follows (empty: the highest release tag)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        secret
      </code>
    </td>
    
    <td>
      The name of the tenant secret that fetches a private source, never its value
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        selector
      </code>
    </td>
    
    <td>
      The node selector it runs under
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        custody
      </code>
    </td>
    
    <td>
      Whose hands the tenant accepted it from: the hub index and listing, the custody epoch and publisher, the forge, repository ID and owner
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        installed_by
      </code>
      
      , <code>
        installed_at
      </code>
      
      , <code>
        updated_at
      </code>
    </td>
    
    <td>
      Who installed it and when
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        signed_by
      </code>
    </td>
    
    <td>
      The hub index whose signed listing the version came from, and who trusted its keys; empty for Git and uploads
    </td>
  </tr>
</tbody>
</table>

An agent that proposes changes to a plugin reads it before it touches anything: the
[Plugin improver](https://docs.oz0.ai/build/plugins/verified/plugin-improver) refuses a target installed from another repository or folder
than its setting names, from another publisher's listing or from a repository in other hands. A target outside the grant
is refused as for every kind, and each read is audited as every read.

## Who and what is left out

- **Another tenant's data, never.** The edge takes the tenant from the node's certificate.
- **A data subject's jobs, never.** Every kind leaves out the jobs started for a data subject and the cases kept from them.
- **No person's figures.** Cost is per agent, version and team, never per person, and no answer names who decided an approval.
- **Small teams.** With [works-council mode](https://docs.oz0.ai/govern/works-council-mode) on (the default), a team's cost shows only when the team has at least the mode's threshold of people (5 by default), and the figures of people's decisions only when at least that many people decided; otherwise they say "too few to show".
- **Secrets and settings' values, never.**

## The grant

A plugin declares the kind in its manifest ([`reads`](https://docs.oz0.ai/reference/plugin-manifest/data#reads)):

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

A person who may read content (`content.read`) and install plugins grants it **for the target plugins they name**. A key never grants it: the reads are answered with a person's permissions.

```bash
oz0 plugin install https://github.com/acme/plugin-improver --accept-reads results --reads-target triage --reads-target billing
```

The install screen, in the console and on the CLI, asks which plugins when you leave the targets out; none, and the plugin reads nothing. Later:

```bash
oz0 plugin reads grant plugin-improver results --target desk --reason "the desk's refused approvals"
oz0 plugin reads revoke plugin-improver results --target billing
```

A target added widens what the plugin reads, so it takes a reason and, when the tenant has [four eyes](https://docs.oz0.ai/govern/reviewed-changes#four-eyes-for-governance-changes) on, waits for another admin; whoever asked is the granting person. Taking a target back applies at once. Both are in the audit log (`plugin.reads.granted`, `plugin.reads.revoked`, with the target). Revoking the kind (`oz0 plugin revoke-reads plugin-improver results`) or removing the plugin drops every target.

A target ends with the plugin it names too: removing `triage`, or installing a plugin named `triage` from another origin, takes it out of every results grant (audited as `plugin.reads.revoked`, with the reason), so a plugin installed later under that name is read only once a person grants it again.

`oz0 plugin dependents triage` lists the plugins that read its results; they do not block its removal. An edge never answers a target from its copy while management is away: the read waits for management.

**The granting person's permissions are read again at every read.** An improver's jobs are usually started by routines and events, so the reads are answered as the person who granted the target, not as whoever started the job. If that person loses `content.read` or leaves the tenant, or their account is disabled, the reads stop; if they lose `findings.read` or `usage.read`, the kinds that need it are left out.

Either way an `evidence_grant_lapsed` finding names the plugin and the target. A person who may read content grants it again with `oz0 plugin reads grant <plugin> results --target <target> --reason "..."`: their permissions answer the reads from then on, which is a loosening (a reason, and four eyes where the tenant has them on), and the finding is resolved.

The finding is also resolved when a read finds the person who granted it holding every permission again.

## The tool

```text
oz.evidence(plugin, agent?, since?, kinds?, limit?, page_token?)
```

- `plugin`: a target the plugin was granted.
- `agent`: one of the target's agents or flows.
- `since`: how far back, such as `7d` or `72h`; 30 days by default, at most 90.
- `kinds`: any of the kinds above; all by default.
- `limit`: rows of each kind, 1 to 50 (20 by default).
- `page_token`: the `next_page_token` of an earlier answer, with the same arguments, in the same job and within a day of the first page. The edge signs each token, so a token the model changes or makes itself is refused.

An answer holds at most 256 KB and 50 rows of each kind; what did not fit comes with `next_page_token`, and a kind whose rows go on says `more`. Pages go at most 1,000 rows deep.

```json
{
  "tenant": "acme", "plugin": "triage", "since": "2026-10-13T09:00:00Z", "until": "2026-10-20T09:00:00Z",
  "content": true,
  "kinds": {
    "findings": {"rows": [{"id": "…", "kind": "eval_regression", "agent": "triager", "state": "open", "message": "…"}]},
    "refused_approvals": {"rows": [{"job": "…", "agent": "triager", "approval": "github.merge", "reason": "the label is wrong"}]},
    "cost": {"rows": [{"agent": "triager", "version": "1.4.0", "runs": 120, "cost_usd": 3.1, "cost_per_run_usd": 0.026}],
             "teams": [{"team": "support", "jobs": 80, "cost_usd": 2.0}, {"team": "legal", "hidden": "too few to show"}]},
    "failure_classes": {"rows": [], "hidden": "not available in this release"}
  },
  "next_page_token": "…",
  "notes": ["a data subject's jobs are left out of every kind"]
}
```

Every agent of a plugin that declares `reads: [results]` is offered the tool (a routine's fire only when its routine lists it); an ACP harness session gets it from its MCP server. The read runs as a step on the job's home node, which asks the edge with the step's own call binding, so no tool process or session can read in a job's name. What the answer holds of other jobs is untrusted data: an answer with content taints the job ([taint](https://docs.oz0.ai/govern/taint-and-labels#what-taints-a-job)).

**Every read is audited**, as `run.evidence_read` with the job, its agent, the target, the kinds and how many rows of each came back, never the content. An agent's proposed change can cite the reads it was based on.

## Its events

The same grant brings the built-in event source **`results`**: routines and flows of the plugin fire on a new finding of a target (`finding.opened`) and on a model change that reaches a target's agents (`model.changed`). See [Event sources](https://docs.oz0.ai/build/parts/event-sources/platform-events#the-results-of-granted-plugins).


## Sitemap

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