Govern

Inventory

Every agent and tool with its owner team and person, purpose, risk class, models and their regions, the data it handles, where its data goes, its DPIA and FRIA fields, a report export, and retired items.

For: tenant admins and governance reviewers.

Use the inventory to record ownership, purpose and risk for the agents, flows and tools in the catalog. It adds the organisational information needed for accountability, including information requested by the EU AI Act and procurement checklists:

FieldWhat it is
OwnerThe team that answers for it, by name: one of the tenant's teams, which cannot be deleted while it owns an entry
Owner personThe person who answers for it, a member of the tenant (user:<name>). An approval's escalation goes to them (see approvals)
PurposeWhat it is for, in a sentence
Risk classminimal, limited or high, after the AI Act's tiers. A high agent keeps its audit entries at least six months
DataThe kinds of data it handles or reaches: personal, financial, health...
Models and regionsThe models an agent's definition names and those its calls used in the last 30 days, each with the region you record for it
EvalsIts plugin's latest quality gate
Terminal
oz0 inventory list
oz0 inventory set agent acme-triage --owner support --owner-person ada --purpose "Sorts incoming tickets" \
  --risk high --data personal --region claude-sonnet-4-5=eu
oz0 inventory set tool acme_github --owner platform --data source-code
Output
KIND   NAME            OWNER     PERSON    RISK  MODELS                                                DATA         PURPOSE
agent  acme-triage     support   user:ada  high  claude-sonnet-4-5 (eu), claude-sonnet-4-5-20250929     personal     Sorts incoming tickets
tool   acme_github     platform  -         -     -                                                     source-code  -

inventory set changes only the flags you give. Admins of the tenant edit the inventory; readers see it. Every change is in the audit log as inventory.set.

An item stays when its agent or tool leaves the catalog, marked so, and an item that a kill switch stops is marked killed.

The console has the inventory under Govern, Inventory: search it, filter it by kind, plugin, risk class and owner, open an agent or a tool to edit it, and download the whole of it as CSV or JSON under Export.

The inventory comes from the server a page at a time: 25 agents and flows by name unless page_size asks for more, at most 100, with the tools whole on the first page (unless --kind tool asks for them alone).

oz0 inventory list reads every page, and searches on the server with --query (words that start the name or a word of the name or description), --plugin and --kind (agent or tool).

The console asks the server for one page at a time too, with its search and every filter (kind, plugin, risk class, owner), and pages below the table; with no kind chosen, the first page also holds every tool, as the API answers it.

Its counts of agents with no owner person, unused for 90 days and with no risk class are each the whole inventory's: the page asks the server for each with its filter and one row, and reads the total.

The report export is always the whole inventory.

Owner people

--owner-person takes an account that is a member of the tenant (or a cluster admin); --owner-person "" takes it away. A person who leaves stops owning: when their membership of the tenant ends, or their account is removed, their items keep their owner team and lose their owner person, and the audit log says so (inventory.owner_cleared).

An agent in the catalog with no owner person is a finding. oz0 doctor shows a row per tenant that has such agents, with their names and the fix, and the Inventory page shows a banner and an Owner person filter (No owner person). Retired agents do not count.

The report

oz0 inventory export writes every item as a report, for an auditor, a procurement questionnaire or a spreadsheet:

Terminal
oz0 inventory export --format csv --out inventory.csv
oz0 inventory export --format json > inventory.json

Each item has its kind, name, plugin and version, state (active or retired, with why), owner person and team, purpose, risk class, data, model regions, whether it is still in the catalog or stopped by a kill switch, and when the agent's latest job started.

An agent's row also has its declared purposes with their legal bases, how many of its jobs in the last 30 days named no purpose, and its recipients (the JSON has the rows and the findings; the CSV counts the rows), so the evidence bundle, which carries this export, carries the recipient map too.

The JSON also says where each agent's models came from and whether the tenant vetted them (model_provenance, by the name the agent uses), and lists every model the tenant's agents may run on, with its provenance and vetting (models): what it is reached through, its publisher, source, endpoint, the model it was edited from, a local model's digest, and who vetted it, when and why.

The Inventory page marks a model nobody has vetted, and Govern, Policy, Models shows them all.

A provenance record says where a model came from; it never says the model is safe.

A cell that a spreadsheet would read as a formula (one that starts with =, +, - or @) is written with a leading apostrophe.

The tenant's readers may export, and every export is in the audit log (inventory.exported). The Inventory page has the same report under CSV and JSON.

Procedures

An agent of a plugin written from an approved procedure (the manifest's procedure:, see Start from a written procedure) shows the day the procedure was approved and the day its review is due. The date is checked against today each time the inventory is read, so when it passes, oz0 inventory list marks the agent [procedure review due <date>] and the Inventory page shows a procedure review due badge, without anyone running lint again. Have the expert review the text, set approved_at, and install the new version.

Retiring and deleting

Retire an agent (or a flow) or a tool to stop new use without removing its plugin:

Terminal
oz0 inventory retire agent acme-triage --reason "replaced by acme-triage-v2"
oz0 inventory unretire agent acme-triage

A retired agent takes no new jobs: the runtime API, routines, webhooks, event routes and delegations refuse them with the reason and the fix. A retired tool's new calls are refused on the nodes within seconds, with the fix (inventory unretire tool). Jobs that run when you retire finish, and so does a call of the tool that is already running: retiring is not an emergency stop. To stop running calls at once, use a kill switch (oz0 kill tool). The reason is required, and both steps are audited (inventory.retired, inventory.unretired).

Removing a plugin retires its agents, flows and tools, so they stay in the inventory, marked, instead of looking active, and installing a version that no longer has an agent or flow retires that one's item the same way. Installing the plugin again, or a version that has the agent again, brings back what was retired so; what a person retired stays retired. An agent of the same name from another plugin starts with a new item instead of the old one's owner, purpose and risk class.

What named an agent that went (API key scopes, agent grants, routines, keep-for rules, legal bases, rules) is marked, dropped or listed when it goes, so that a new agent of the same name inherits none of it (removing a plugin says which). The list stays, for the tenant's admins, since it names keys and people's grants:

Terminal
oz0 inventory tombstones
# Agent billing of plugin payments went 2026-10-13 09:12 (by user:ada); what named it:
#   marked api_key 01J9...: key billing-app: its scope for billing reaches nothing now; make a new key for a new agent
#   dropped keep_for billing: keep-for rule of 3650 days; the jobs it left keep their class
#   listed tool_rule rule-3f2a: its condition names billing; edit the rule (a governance change) if a new agent of the name should not meet it

Unused agents

An agent or flow whose plugin was installed more than 90 days ago and that started no job in 90 days, at the top of a tree or delegated, is marked unused 90 days: a candidate to retire. The Inventory page has an Unused for 90 days filter and a banner that counts them, oz0 inventory list --unused lists them, and oz0 doctor shows them per tenant with the command that retires one.

The finding names agents and dates only, never who used an agent or how often. It is as good as the job history the tenant keeps: with a shorter retention, it says so.

Badges from what an agent reaches

An item's badges say what is wrong with its plugin or a plugin it reaches through grants: retired via a retired plugin, vulnerable via one with a known vulnerability, retired version via one whose installed hub version its index deprecated, withdrew or quarantined, with the chain when it is further away. inventory list prints them after the name, and the API returns them as via.

Once its plugin is gone, an item's row can go too:

Terminal
oz0 inventory delete agent acme-triage

Its audit entries stay, and the audit chain keeps verifying. delete is refused while a plugin of the tenant still has the agent or tool: remove the plugin, or retire the item. It is audited as inventory.deleted.

The capability card

Each agent has a card that says what it may do, generated from its plugin, never from what its author wrote about it. The inventory shows it when you open an agent, and so does the agent's page (its Permissions tab, under More); plugin inspect prints it before an install, and a hub listing carries it.

On the cardWhere it comes from
Takes and returnsThe agent's contracts: text, or a JSON object with its schema
Tools, by pluginEvery function the agent may call, grouped by the plugin that provides it, each counted as read-only, write or destructive from its MCP annotations (readOnlyHint, destructiveHint). A function without annotations counts as destructive, as MCP says; one no node has listed yet counts as not listed
Needs approvalThe functions that wait for a person: requires_approval in the manifest, and the tenant's tool policy
Delegates to, asks peopleThe agents it may start as children, and whether it may ask people questions (oz.ask_user)
Per requestThe estimated tokens its tool definitions add to every model request (see the catalog). For an agent that loads its tools lazily, what its requests carry (the two discovery tools and its built-ins), with what every function it lists would add beside it
Version stateHow far its plugin's version was checked: saved, validated or published (see Indexes)

Annotations are a tool's own hints about itself. The card shows them as they are; the snapshot keeps a tool from changing them after its first listing, and the tool policy decides what each class may do.

Deployment profile

An agent's entry also shows how people oversee it, in the console's dialog for the agent and with oz0 inventory list --oversight <agent> --json as oversight (it is computed for the one agent asked for, since it reads that agent's approval decisions of 90 days):

  • Test approvals caught: of the agent's test approvals decided in the last 90 days, how many people caught;
  • Agreement per action class: how often people approved the agent's proposed calls of each class as proposed, since its latest drift event or over 90 days, with the shadow rules' sums beside it and whether the promotion threshold (95% over 50 decisions) holds (agreement).

Both go through works-council mode: figures decided by fewer people than its threshold are hidden. They are data: nothing changes an agent's level from them.

Plugins that read the inventory

A plugin may read the inventory, to keep another system in step with it: a plugin whose manifest declares reads: [inventory] gets the tenant's agents and flows in each call of its functions, and its routines and flows fire on the inventory's changes. Each is a permission a tenant admin grants at install, and takes back without removing the plugin:

Terminal
oz0 plugin install ./registry --accept-reads inventory
oz0 plugin list                                   # registry reads inventory (granted)
oz0 plugin revoke-reads registry inventory

Such a plugin reads each agent's name, plugin and version, description, owner team and person, state (and why and when it was retired), risk class and purpose, and nothing else: not its models, regions or kinds of data, its eval results, its jobs or the tenant's members. It writes nothing back.

Its reads are audited (inventory.read, at most once per plugin and ten minutes), and so are the grant and its revocation. An owner person's name is personal data: a plugin that reads it may send it where the plugin sends what it reads, so check what a plugin does with it before you grant it.

The changes come as events of the built-in source inventory (the inventory's changes): created, updated, retired, unretired and deleted, within about ten seconds of the change.

Where data goes

oz0 inventory recipients shows, for each agent and flow, every place its jobs can send data. It is computed from the records that route the data, never typed, and every row says where it came from:

KindWhat it isSource
model_providerA provider its model, fallbacks or model alias can resolve to, with the provider record's region, whether it keeps data after a call, whether it is the node-local provider, its base URL's host, and why the tenant's data rules refuse it, if they do. A provider its calls used in the last 30 days (from the metered calls) is marked observedmodel.primary, model.fallback, alias:<name>, llm_usage
egress_hostHosts its sandboxed sessions may reach: the plugin's egress, the agent's own, and the tenant's (a row without an agent)manifest:egress, agent:egress, tenant:egress
remote_toolA remote MCP server one of its tools is, by URLmanifest:tools.url
credential_hostHosts the platform puts a secret on: the manifest's inject, with the secret's name, and the tenant's credential bindings, with the connection a binding serves but not the tenant secret's name. A connection's binding is listed only for the agents of plugins that declare that connection, as the edge applies it. Never a valuemanifest:requires.inject, credential_binding:<id>
connection_hostThe hosts of its plugin's connections: their steps' and token URLsmanifest:connections
publisherWho publishes its plugin: the hub namespace, or not from a hub with where it was installed from (a URL without its credentials)plugin:hub, plugin:source
grantThe plugins its plugin's grants (uses:) reach, and the plugins those reach, each with its publishergrant:<plugin>
notification_targetNotification rules that carry its facts: a webhook's host, a channel, or email to roles, teams or a number of peoplenotification_rule:<id>
outside_spendPlugins the tenant lets import outside spend, and the sources it holds (rows without an agent)plugin_provides:outside-spend, outside_spend
secret_storeThe secret stores the tenant's edges offer it: an edge asks one for the value of a secret reference, by its path (a row without an agent)secret_stores
inventory_readerPlugins granted reads: [inventory]: their calls carry the tenant's inventory, its agents' owners among it (a row without an agent)plugin_reads:inventory
token_cloudA service or cloud that receives the platform's identity tokens for an agent's calls: the audience of a connection of kind identity or on_behalf_of, and an on_behalf_of connection's token endpoint; the row says when the token names a personmanifest:connections
outside_agentAn outside A2A agent the agent is: its card's URL and host, which receive the job's input and the answers to its questions, the auth kind and the plugin secret it uses, and the token endpoint it gets a token from. Where the tenant's plugin settings fill in the host, the row shows the plugin's egress hosts it can be, such as *.services.ai.azure.comagent:harness.card, agent:harness.auth.token_url
Terminal
oz0 inventory recipients --agent acme-triage
oz0 inventory recipients --format json --out recipients.json
oz0 inventory recipients --format csv --out recipients.csv
Output
AGENT        KIND            NAME            HOST               REGION  NOTE                                   SOURCE
acme-triage  model_provider  anthropic       api.anthropic.com  us      observed; anthropic/claude-sonnet-4-5  model.primary
acme-triage  model_provider  anthropic       -                  eu      declared; claude-sonnet-4-5            inventory:regions
acme-triage  remote_tool     kb              kb.example.com     -       -                                      manifest:tools.url
acme-triage  publisher       not from a hub  -                  -       git https://github.com/acme/triage     plugin:source
finding region_differs: the inventory says claude-sonnet-4-5 runs in eu, and provider anthropic's record says us

The regions people record with inventory set --region stay, as declared rows beside the computed ones. A declared region that differs from the provider record's is a finding (region_differs), not an error: fix the record or the inventory.

The map is as good as the records. An installed plugin whose stored bundle cannot be read is a finding of the map (bundle_unreadable): the map cannot say where that plugin's agents and flows send data until it is installed again. It does not see a plugin's own code reaching a host outside the sandbox, a remote tool's server passing data on, or a provider record with the wrong region. Agents that a flow's steps start, delegated children and colleagues appear under their own names.

The JSON export names its format (oz0-recipients/1) and the row kinds; the CSV writes cells a spreadsheet would read as formulas with a leading apostrophe. The tenant's readers may see and export it, and every export is audited (inventory.recipients.exported). The Inventory page shows an agent's map when you open the agent, with CSV and JSON.

Purposes

An agent's definition declares what its jobs are for (purposes: [support], see the agent definition), and each start may name one; the tenant keeps its own lists of purposes and legal bases. Purposes and legal bases says what the platform checks and what it leaves to you.

Terminal
oz0 inventory purposes

lists the tenant's legal bases and purposes, every agent's declared purposes with the legal basis that holds for each, its jobs in the last 30 days and how many named no purpose, and the findings: jobs_without_purpose (an agent that declares purposes still gets jobs without one), purpose_not_listed (it declares a purpose the tenant's list does not have), no_legal_basis and unknown_legal_basis. The counts are per agent and purpose, never per person. The Inventory page has the lists under Purposes and legal bases.

Assessment fields

oz0 inventory assessment answers, for one agent, the fields a data protection impact assessment (GDPR Art. 35(7)) and a fundamental rights impact assessment (AI Act Art. 27(1)) ask about:

FieldFilled from
processing_descriptionThe agent's description, the inventory's purpose, its plugin and version; typed text beside them
purposes_and_legal_basesIts declared purposes, their legal bases and job counts
data_categoriesThe inventory's data
data_subject_categoriesTyped
recipientsThe recipient map
retentionThe tenant's retention, its legal hold and the high-risk floor (for the tenant's admins, who alone read the retention rule; readers see where to find it), the agent's keep_for
necessity_and_proportionality, risks_to_rightsTyped
measuresSettings: payload encryption, data subjects' keys, data rules, the tool policy, tools that need approval, oz.ask_user, four eyes, works-council mode, the AI label, kill switches, egress; typed text beside them
fria_deployer_processThe owner team and person, the routines, triggers and channel bindings that start it; typed
fria_period_and_frequencyIts jobs in the last 30 days and its last run; typed
fria_affected_personsTyped
fria_specific_risksIts risk class; typed
fria_human_oversightApprovals, oz.ask_user, four eyes; typed
fria_if_risk_materialisesKill switches and retiring; typed

Every filled value names its source. What the platform cannot know, an admin types:

Terminal
oz0 inventory assessment acme-triage
oz0 inventory assessment acme-triage --set data_subject_categories="customers who open tickets"
oz0 inventory assessment acme-triage --format json --out acme-triage-fields.json

A typed field holds at most 4,000 characters; an empty text clears it, and each change is audited (inventory.assessment.set). The JSON export (oz0-assessment-fields/1) is audited too (inventory.assessment.exported). These are fields and their sources, for the assessment you write with them: the platform generates no assessment or report, and says nothing about whether one is enough.

Copyright © 2026