Skip to content

Authorisation Warrants

A warrant is the authority an agent carries with it. Until now the Authorisation Layer decided every action by looking up what the agent may do in its own records. A warrant turns that authority into a short-lived signed object the agent holds: what it may do, within which bounds, on whose Charter, until when, and through which delegation chain. Any tool, MCP server or self-hosted relay can check it offline with Xybern's published public key, so enforcement no longer needs a round trip to Xybern, and a delegate can never hold more than the agent that delegated to it.

Warrants do not replace the Charter. Every action still passes the rules. A warrant is an envelope: it says what an agent is allowed to attempt, the Charter still decides each attempt.

In one minute

from xybern import Xybern
from xybern.warrants import WarrantHolder

client = Xybern(api_key="xb_live_...")

holder = WarrantHolder(client, agent_id="agt_payments")      # re-mints at 80 percent of the TTL
decision = client.intercept("send_payment", "Refund 120 to #4411",
                            agent_id="agt_payments", metadata={"amount": 120},
                            warrant=holder.token())

Or by hand:

w = client.issue_warrant("agt_payments", ttl_seconds=900,
                         narrow={"action_families": ["send_*", "read_*"],
                                 "argument_bounds": {"send_payment": {"amount": {"max": 500}}}})
token = w["token"]                     # "xwt1.…", pass as warrant= or the Xybern-Warrant header

The default TTL is 15 minutes (XYBERN_WARRANT_TTL_SECONDS on the issuer). A warrant is always bound to one agent, and to the agent's credential when it has one, so it cannot be lent.

What goes into a warrant

The issuer derives the authority from the objects in force at that moment:

Source Contributes
Charter version charter_hash, so the receipt can say which Charter the warrant was issued under
Access profile (active) action_families from the allowed and held actions
Intent contract (active) action families and per-action amount caps, the contract's action budget
Runtime session session_id binding and the remaining action budget
Credential holder fingerprint and scopes, expiry floor
A narrow you pass Any further narrowing. Narrowing is an intersection: you can only ask for less

With no profile or contract the families are ["*"] and a caveat says standing authority applies. That is honest: the agent has no declared box, so the warrant covers whatever the Charter allows.

Enforcement

At the choke point (POST /v1/enforce/intercept, the MCP gateway, the relay):

  • a presented warrant is always checked. If it does not hold for this agent, action, bounds, session or window, the action is refused with decision_path: "warrant" before any rule is evaluated. A claimed authority that does not hold is worse than none.
  • an agent that requires warrants is refused without one. Set it per agent in the dashboard (Warrants view, "Who must carry a warrant") or PUT /v1/enforce/agents/<id> with {"delegation_policy": {"warrant_required": true}}. The workspace default is settings.features.warrants_required; agents with no explicit setting follow it.
  • an MCP server can require warrants for every tools/call (warrant_required on the server, header Xybern-Warrant).
  • a valid warrant is recorded on the decision (warrant), in the lineage and receipt (authorisation.warrant), and sealed in the Vault entry. The plain-language lineage says "under warrant wrt_… (delegation depth n)".
  • the issuer counts uses against budgets.max_actions.

Delegation attenuates

When agent A delegates to agent B (POST /v1/enforce/delegate) and the grant is active, the issuer mints B a child warrant narrowed from A's own warrant (minted first if A holds none): the grant's action types and scopes, max_amount as an argument bound, max_uses as the action budget, expiry no later than the grant's. The response carries it as warrant_token. The child records its parent's body hash in chain and its depth; the issuer refuses to sign any child that would widen the parent. Revoking the grant, the parent warrant, the session, the credential or the agent revokes the child.

Revocation

  • POST /v1/enforce/warrants/<id>/revoke revokes a warrant and its descendants.
  • Killing a session, revoking a credential, revoking a grant, and Revoke everywhere on an agent all revoke the warrants they cover, and the revocation certificate lists them.
  • GET /v1/enforce/warrants/revocations?since=<seq> is a signed, incremental list a relay or long-lived tool pulls. Every revocation is sealed to the Vault.

Checking a warrant without Xybern

from xybern.warrants import verify_offline, covers_action
keys = client.warrant_keys()["keys"]           # or GET /api/sentinel/vault/keys, cache it
res = verify_offline(token, keys)              # signature, format, window
ok = res["valid"] and covers_action(res["body"], "send_payment", {"amount": 120})
python tools/xybern-verify/verify.py --warrant "$TOKEN" --keys keys.json --action send_payment --metadata '{"amount": 120}'

Offline checks cover everything except revocation and budget, which are issuer state; ask POST /v1/enforce/warrants/verify or use the revocation list for those. The format is specified in Authorisation Warrant v1.

Self-hosted relay

The relay checks warrants on your own host: it pulls the issuer's keys, the signed revocation list and the list of agents that require warrants (XYBERN_WARRANT_REFRESH, default 30 seconds), and refuses warrant-bearing actions if the revocation list is older than XYBERN_RELAY_REVOCATION_GRACE_SECONDS (default 60) unless XYBERN_FAIL_OPEN=1. Relay MCP servers accept "warrant_required": true in XYBERN_MCP_SERVERS. GET /v1/status on the relay reports the warrant cache state.

API

Method Path Purpose
POST /v1/enforce/warrants Issue: {agent_id, session_id?, contract_id?, ttl_seconds?, narrow?, parent_warrant_id?}
GET /v1/enforce/warrants List (agent_id, status, grant_id, session_id)
GET /v1/enforce/warrants/<id> One warrant, with its token while active
POST /v1/enforce/warrants/<id>/revoke Revoke (cascade by default)
POST /v1/enforce/warrants/verify Check a token against issuer state, optionally for an action
GET /v1/enforce/warrants/keys Published signing keys
GET /v1/enforce/warrants/revocations Signed incremental revocation list

SDK 2.5.0: issue_warrant, list_warrants, get_warrant, revoke_warrant, verify_warrant, warrant_keys, warrant_revocations, intercept(warrant=...), and xybern.warrants.WarrantHolder, verify_offline, covers_action.