A tool capability is an unforgeable grant that says exactly which operation an agent may perform, on which resources, for which tenant and purpose, until when, under which limits. The tool service validates that grant on every call. This is safer than giving the agent a broad API token and relying on a prompt to keep calls within scope.
“Capability” is used here as an architectural pattern, not a claim that one universal agent-capability standard exists. It follows least-privilege and zero-trust principles: protect resources directly, authenticate the actor, and make authorization specific to the requested action (NIST SP 800-207).
Replace ambient authority with explicit grants
Ambient design:
agent process has SUPPORT_ADMIN_TOKEN
└── can read every ticket, export customers, and send replies
Capability design:
user asks to handle ticket T-381
policy service issues C-92:
tenant = acme
operations = read_ticket, create_reply_preview
resources = ticket/T-381
expires = 10 minutes
max_calls = 8
agent presents C-92 to each tool
tool independently validates C-92
The second design limits the blast radius if model output is manipulated. It also produces a concrete object for audit, revocation, and testing.
Define a capability contract
type ToolCapability = Readonly<{
id: string;
issuer: string;
subject: string;
tenantId: string;
taskId: string;
audience: string;
operations: readonly string[];
resources: readonly string[];
constraints: Readonly<{
expiresAt: string;
notBefore?: string;
maxCalls: number;
maxResultBytes: number;
allowedTransitions?: readonly string[];
}>;
parentId?: string;
policyVersion: string;
}>;
This is application pseudocode. subject identifies the agent runtime or workload; separate fields should retain the human principal and client when the capability represents delegated user authority. audience prevents a grant for the ticket service from being replayed at the email service.
Do not put a bearer capability in model-visible context. The runtime should hold an opaque handle and attach the real credential after policy validation.
Issue capabilities from verified intent
The model must not create its own grant. A trusted policy service issues one after:
- authenticating the human and agent workload;
- resolving the active tenant server-side;
- authorizing the user for the requested resource;
- mapping the user request to a reviewed workflow policy;
- removing unnecessary operations and resources;
- applying expiry, call, byte, cost, and state limits; and
- recording the grant and reason.
For a high-impact action, do not include commit authority at task start. Issue read and preview capabilities first. After the user approves the exact preview, mint a one-use commit capability bound to its digest.
{
"operation": "send_approved_reply",
"resource": "ticket/T-381",
"previewDigest": "sha256:…",
"recipient": "customer_812",
"maxUses": 1,
"expiresAt": "2026-07-18T19:10:00Z"
}
The digest is illustrative. Define a canonical representation before hashing so semantically identical fields are encoded consistently.
Choose reference or self-contained capabilities deliberately
A reference capability is a random opaque ID stored server-side. It is easy to revoke and update, but every tool call needs a lookup and the store is an availability dependency.
A self-contained capability is signed and locally verifiable. It can reduce lookup latency but is harder to revoke immediately, and its claims may leak metadata if merely signed rather than encrypted. The verifier must pin issuer, audience, algorithm, key, lifetime, and exact claim semantics.
OAuth access tokens can carry similar restrictions. RFC 8707 defines resource indicators for audience targeting, while OAuth security BCP RFC 9700 recommends restricting token privileges and supports sender-constrained tokens to reduce replay. Those standards do not automatically define your domain resources or operation policy.
When immediate revocation matters, combine short-lived self-contained grants with a revocation or status check for sensitive commits. Do not assume “short-lived” is sufficient if a minute of misuse is unacceptable.
Attenuate rather than amplify
A supervisor may delegate a subset to a worker:
parent: read tickets T-1..T-20, 20 calls, expires 20:00
child: read ticket T-7, 3 calls, expires 19:45
The child must not add operations, resources, duration, audiences, or call budget. Validate that every child dimension is a subset of its parent and retain the delegation chain for audit.
Do not allow agents to exchange bearer capabilities through chat. Use a trusted delegation service that authenticates both workloads and binds the child grant to its recipient. A malicious peer's artifact must never be interpreted as authority.
Enforce at the tool, not only the orchestrator
async function authorizeToolCall(
capabilityHandle: string,
request: ToolRequest,
runtime: VerifiedRuntime,
) {
const grant = await capabilityStore.resolve(capabilityHandle);
requireActive(grant);
requireAudience(grant, "ticket-service");
requireSubject(grant, runtime.workloadId);
requireTenant(grant, runtime.tenantId);
requireOperation(grant, request.operation);
requireResource(grant, request.resourceId);
await consumeCallBudgetAtomically(grant.id);
}
The code is illustrative. Consumption must be atomic so concurrent calls cannot overspend. Resource checks should use normalized internal IDs, not path-prefix strings vulnerable to traversal or encoding ambiguity.
The tool still validates arguments and business state. A grant to refund invoice I-9 does not make a negative amount or already-refunded invoice valid.
Bind capabilities to state transitions
Prefer a state machine over broad CRUD:
draft → previewed → approved → committed
└──────→ expired
A capability can permit previewed → approved or approved → committed once. The tool reads the authoritative current state and applies the transition with optimistic concurrency. This prevents a model from skipping approval or replaying an old commit after the object changed.
Use idempotency separately. A capability limits authority; an idempotency key makes duplicate delivery return the same outcome. You need both for ambiguous network failures.
Handle revocation, expiry, and outages
Record a revocation reason and reject future use. Propagate revocation to caches quickly enough for the risk tier. If the capability service is unavailable, fail closed for writes and sensitive reads. A carefully defined read-only degraded path may be acceptable for public data, but it must not silently grant broader authority.
Use monotonic server time for duration measurement where possible and synchronized wall time for audit. Reject capabilities outside the allowed clock tolerance. Never let a client choose an unlimited expiry.
Rate limits, cost budgets, and concurrency limits should apply even to valid grants. Authorization does not guarantee resource availability.
Threats to test
- A capability is presented to the wrong audience.
- The model changes tenant or resource in arguments.
- A child grant exceeds its parent scope or expiry.
- Concurrent calls exceed a one-use or call-count limit.
- A revoked grant remains in a verifier cache.
- A commit uses a changed preview body.
- An old capability is replayed by another runtime.
- A self-contained token uses an unexpected algorithm or issuer.
- Logs or model traces expose the bearer value.
- Capability-service failure accidentally falls open.
Sender-constrained OAuth tokens using DPoP can reduce use of stolen tokens by binding them to a key, but DPoP is not itself authorization and does not provide message integrity beyond its defined proof (RFC 9449).
Release checklist
- Issue grants from verified user intent and policy, never model claims.
- Bind subject, human principal, tenant, audience, task, operation, and resources.
- Apply short expiry and atomic use, byte, cost, and concurrency limits.
- Keep bearer values out of prompts, results, logs, and memory.
- Attenuate every delegation and preserve its chain.
- Validate the grant again inside each tool service.
- Bind commits to approved immutable details and authoritative state.
- Implement revocation, cache bounds, and fail-closed behavior.
- Pair authorization with idempotency and audit events.
- Test replay, substitution, concurrency, and cross-tenant failures.