Agent Identity & Policies
An agent identity turns an autonomous agent into a first-class security principal the gateway can decide, enforce and prove against. The model is actions, not access: instead of handing an agent a key that can do anything its group can do, you grant it specific action types on specific targets — and everything else is denied by default.
Brutor is deliberately not an identity provider. Issuance stays with your IdP (Okta, Entra Agent ID, SPIFFE); Brutor holds the mapping from IdP subject to agent, and the policy attached to it.

Identities are defined under Resources → Agents → Identities; their grants live under Governance → Agent Policies (also editable per resource group).
The identity
Section titled “The identity”
| Field | Values | Notes |
|---|---|---|
name |
unique per tenant | |
owner_user_id / owner_label |
The accountable human sponsor | |
idp_server_id + idp_subject |
The IdP workload seam — e.g. spiffe://acme/ns/agents/billing; unique per IdP server |
|
source |
manual · idp · discovered |
How the identity entered the platform |
default_deny |
default true |
No matching grant → deny |
enforcement_mode |
enforce · dry_run |
dry_run logs would-block verdicts and lets traffic proceed |
status / enabled / expires_at |
active · suspended |
Revocation sets suspended + revoked_at |
How a caller becomes this agent — two ways, no bespoke headers:
- API key binding — an API key with
agent_idset: every call with that key acts as the agent. - IdP workload token — the agent presents its own OIDC/SPIFFE bearer token; the gateway verifies signature, issuer and expiry against the tenant IdP’s JWKS and resolves the agent by
(idp_server_id, sub).
Resource-group membership is separate from grants: adding the agent to a resource group applies that group’s ACLs, limits, guardrails and approval rules — the same stack a human member gets.
Grants — actions, not access
Section titled “Grants — actions, not access”A grant is (action_type, target, effect) plus optional constraints:
| Values | |
|---|---|
action_type |
llm_call · mcp_tool · a2a_call · skill_exec |
target |
Glob over the model/tool/card/skill name — hubspot:*, db:delete_rows, * |
effect |
allow · deny · approval_required |
constraints |
time_window, max_delegation_depth (A2A depth cap), rate (per-grant rate limit), deny_tool_risk |
Tool risk classes (read_only, idempotent, destructive, open_world) are derived from MCP tool annotations — with conservative defaults: a tool with no annotations is treated as destructive + open_world, so deny_tool_risk: ["destructive"] blocks unannotated tools too.
# Create the identitycurl -s -X POST http://localhost:5050/v1/admin/agent-identities \ -H "Authorization: Bearer $ADMIN_JWT" -H "Content-Type: application/json" \ -d '{"name": "billing-bot", "owner_label": "RevOps", "idp_server_id": "idp-01K0...", "idp_subject": "spiffe://acme/ns/agents/billing"}'
# Grant: read CRM freely, updates need a human, deletes nevercurl -s -X POST http://localhost:5050/v1/admin/agent-identities/agent-01K0.../grants \ -H "Authorization: Bearer $ADMIN_JWT" -H "Content-Type: application/json" \ -d '{"action_type": "mcp_tool", "target": "hubspot:*", "effect": "allow"}'{ "id": "grant-01K0...", "agent_id": "agent-01K0...", "action_type": "mcp_tool", "target": "hubspot:*", "effect": "allow", "enabled": true}How the gateway decides
Section titled “How the gateway decides”On every governed surface (LLM, MCP tool, A2A, skills), when the caller is an agent principal:
- Strictest matching grant wins — among live grants (enabled, unexpired, inside
time_window) matching the action + target,denybeatsapproval_requiredbeatsallow. - Default-deny — no matching grant and
default_deny: true→ deny; withdefault_deny: falsethe decision falls through to the resource-group ACL. - Strictest-wins merge with the group ACL — the agent must satisfy its own grants and its resource group’s rules; an explicit deny from either plane wins, and
approval_requiredbeats allow. - Constraints only tighten — delegation depth, tool-risk denies and per-grant rate limits apply after the effect is resolved.
A blocked call returns HTTP 403 with agent_not_authorized: <reason>; policy-lookup failures fail closed. The decision, matched grant and reason are stamped on the proxy log (actor_agent_id records the acting agent) — every decision is evidence.
Base: /v1/admin/agent-identities on the Control Plane (:5050).
| Method | Path | Purpose |
|---|---|---|
| GET / POST | /v1/admin/agent-identities |
List (filters: status, q) / create |
| GET / PATCH | /v1/admin/agent-identities/{agent_id} |
Detail / update |
| POST | /v1/admin/agent-identities/{agent_id}/revoke |
Revoke (disable + suspend) |
| GET / POST | /v1/admin/agent-identities/{agent_id}/grants |
List / add grants |
| DELETE | /v1/admin/agent-identities/{agent_id}/grants/{grant_id} |
Remove a grant |
| GET / POST | /v1/admin/agent-identities/{agent_id}/memberships |
List / add resource-group memberships |
| DELETE | /v1/admin/agent-identities/{agent_id}/memberships/{member_id} |
Remove membership |
Related
Section titled “Related”- A2A agents — agent cards (what an agent exposes to peers) vs identity (who it is, what it may do)
- Tool approvals — where
approval_requiredgrants route - Resource groups — the group plane the agent decision merges with
- AI Asset Registry — identities appear as
agent_identityassets
