---
title: Capabilities
description: The static grant that says what an agent exists to do, and why it is separate from the tool registry.
---

A capability is a named thing an agent is allowed to do: `refund.create`,
`order.read`, `database.query`, `email.send`. It is the coarsest layer of
authority and the one you set when you register the agent.

Capabilities answer *"what is this agent for?"*. Policy answers *"under what
conditions, right now?"*. Keeping those separate is what lets you tighten
conditions without re-provisioning agents.

## Naming

`resource.action`, lower case, dotted. The convention is not enforced by a
validator but it is worth keeping, because capability names end up in policy
rules, in delegation constraints, in passports and in audit queries, and a
consistent namespace is the difference between a readable policy and a guessing
game.

| Good | Why |
| --- | --- |
| `refund.create` | Clear resource, clear action |
| `order.read` | Reads and writes are separable |
| `database.query` | Narrower than `database.*` |
| `email.send` | Obvious blast radius |

| Avoid | Why |
| --- | --- |
| `admin` | Grants everything and explains nothing |
| `refunds` | Is that read, create, or cancel? |
| `doStuff` | A rule that mentions this is unreviewable |

## Granting

At registration:

```bash
curl -X POST https://veefore.example.com/v1/agents \
  -H "Authorization: Bearer $VEEFORE_API_KEY" \
  -H 'content-type: application/json' \
  -H 'Idempotency-Key: register-finance-agent' \
  -d '{
    "name": "finance-agent",
    "workspace_id": "wsp_01JAV…",
    "environment_id": "env_01JAV…",
    "capabilities": ["refund.create", "order.read"],
    "public_key": "MCowBQYDK2VwAyEA…"
  }'
```

or with the CLI:

```bash
veefore init --name finance-agent \
  --capability refund.create \
  --capability order.read
```

Changing them later needs `agent:manage_capabilities`, and the change is an
audit event. Capability grants are not something that drifts quietly.

## Capability and tool are different things

This is the part worth slowing down on, because it is the most common source of
confusion.

A **capability** is an abstract permission held by the agent.
A **tool** is a concrete, registered, callable thing with an endpoint, a
destination allowlist and a risk rating.

A tool declares which capability it requires. An agent can call a tool only when
**both** are true:

1. The agent holds the capability the tool requires, and
2. The agent is explicitly authorized for that specific tool.

```bash
# Register the tool — grants nothing to anyone
POST /v1/tools                        { "key": "refund_customer", "required_capability": "refund.create", … }

# Authorize this agent for this tool — the second, separate step
POST /v1/tools/{toolId}/agents        { "agent_id": "agt_01JAV…" }
```

:::security Why two steps instead of one
If holding `refund.create` automatically granted access to every tool that
declares it, then registering a new refund tool would silently widen what every
existing finance agent could reach. A tool registration would become a privilege
escalation.

With the steps separated, adding a tool grants nobody anything until a human
authorizes an agent for it. The same reasoning drives MCP tool discovery, where
a newly discovered tool is unauthorized until approved — see [MCP](/build/mcp).
:::

The two refusals are distinct on purpose, so you can tell which half is missing:

- `CAPABILITY_NOT_GRANTED` — the agent does not hold the capability.
- `TOOL_NOT_AUTHORIZED` — it holds the capability but is not authorized for this tool.

## What the agent sees

At handshake the gateway tells the agent exactly what it may call:

```ts
const info = await agent.handshake();

console.log(info.capabilities);
// ['refund.create', 'order.read']

console.log(info.authorizedTools);
// [{ key: 'refund_customer', name: 'Refund a customer order',
//    capability: 'refund.create', status: 'ENABLED' }]
```

Declaring your intended tools up front turns a mid-task failure into a startup
warning:

```ts
const agent = protect(myAgent, {
  declaredTools: ['refund_customer', 'order_lookup'],
  eagerHandshake: true,
});
```

Anything declared but not authorized is reported at handshake. The handshake is
advisory, not a cache — every action is still decided independently.

## Capabilities in policy

```json
{
  "id": "rule-refund-requires-capability",
  "name": "Refunds need the refund capability",
  "priority": 800,
  "enabled": true,
  "when": {
    "kind": "group",
    "id": "g-refund-capability",
    "operator": "and",
    "children": [
      {
        "kind": "condition",
        "id": "c-capability-is-refund",
        "field": "capability",
        "operator": "eq",
        "value": "refund.create"
      },
      {
        "kind": "condition",
        "id": "c-agent-lacks-refund",
        "field": "agent.capabilities",
        "operator": "not_contains",
        "value": "refund.create"
      }
    ]
  },
  "then": { "decision": "BLOCK", "reason": "Agent does not hold refund.create." }
}
```

In practice you rarely need that rule — stage 5 of the pipeline already enforces
it. It is useful when you want a *different* reason recorded, or when you want
to block a capability an agent technically holds under specific conditions.

The genuinely useful pattern is testing the capability of the current request:

```json
{ "field": "capability", "operator": "in", "value": ["refund.create", "payout.initiate"] }
```

## Related

:::cards

:::card Delegated authority | /concepts/delegation
Narrowing a capability for a bounded period.
:::

:::card Register a tool | /build/register-a-tool
The tool side of the pair, in detail.
:::

:::card Policy fields | /reference/policy-fields
`capability`, `agent.capabilities`, `authority.capabilities`.
:::

:::card Tools API | /reference/api/tools
Registration, authorization and disabling.
:::

:::
