Veefore docs

Capabilities

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…" }

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.

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"] }

Identity, authority, policy and enforcement for autonomous AI agents.