A2A Delegation
Agent-to-Agent Delegation (A2A Auth)¶
When Agent A tells Agent B to perform an action, both sides must be authorised. Xybern intermediates every agent-to-agent interaction with bidirectional authorisation, scope attenuation, and cryptographic delegation grants recorded in the Provenance Vault.
Info
Why this matters: Multi-agent frameworks (CrewAI, AutoGen, LangGraph) let agents freely call each other with zero authorisation. Xybern ensures Agent A is authorised to delegate, Agent B is authorised to accept, and the delegated scopes are the intersection of both, not a superset.
Core Concepts¶
| Concept | Description |
|---|---|
delegation_policy |
Per-agent config: can_delegate, can_accept_delegation, delegable_scopes, acceptable_scopes, max_delegation_depth |
| Scope Attenuation | Granted scopes = source's delegable ∩ target's acceptable ∩ requested. Wildcards supported (trade:*). |
| Delegation Grant | First-class verifiable token (dlg_...) that the target agent presents when acting on behalf of the source. |
| Delegation Chains | Agent B can re-delegate to Agent C if depth allows. Max depth enforced, cascading revocation propagates down the chain. |
Register Agents with Delegation Policy¶
import requests
BASE = "https://xybern.com/api/v1"
HEADERS = {"X-API-Key": "xb_...", "Content-Type": "application/json"}
# Source agent - can delegate trade:read and db:read
requests.post(f"{BASE}/enforce/agents", headers=HEADERS, json={
"name": "finance-agent",
"framework": "crewai",
"permissions": {"allowed_action_types": ["execute_trade", "query_database"]},
"scopes": ["trade:write", "trade:read", "db:read", "agent:delegate"],
"delegation_policy": {
"can_delegate": True,
"can_accept_delegation": False,
"delegable_scopes": ["trade:read", "db:read"],
"max_delegation_depth": 3
}
})
# Target agent - accepts trade:read and db:read
requests.post(f"{BASE}/enforce/agents", headers=HEADERS, json={
"name": "analyst-agent",
"framework": "crewai",
"permissions": {"allowed_action_types": ["query_database", "read_data"]},
"scopes": ["trade:read", "db:read"],
"delegation_policy": {
"can_delegate": False,
"can_accept_delegation": True,
"acceptable_scopes": ["trade:read", "db:read"]
}
})
POST /v1/enforce/delegate¶
Request a delegation grant from one agent to another.
resp = requests.post(f"{BASE}/enforce/delegate", headers=HEADERS, json={
"source_agent_id": "agent_abc123",
"target_agent_id": "agent_def456",
"scopes": ["trade:read", "db:read"],
"action_types": ["query_database"],
"constraints": {"max_amount": 100000},
"instruction": "Analyse Q1 trading performance",
"ttl_hours": 12,
"max_uses": 5
})
grant = resp.json()["grant"]
# grant["grant_id"] → "dlg_42eb33adab69"
# grant["attenuated_scopes"] → ["db:read", "trade:read"]
# grant["delegation_depth"] → 1
# grant["vault_entry_id"] → "ve_590dba34..."
POST /v1/enforce/delegate/verify¶
Verify a delegation grant before executing an action.
resp = requests.post(f"{BASE}/enforce/delegate/verify", headers=HEADERS, json={
"grant_id": "dlg_42eb33adab69",
"agent_id": "agent_def456",
"action_type": "query_database"
})
# resp.json()["valid"] → True
# resp.json()["reason"] → "Grant verified"
Using a Grant with Intercept¶
Pass grant_id to /enforce/intercept so the control plane verifies the delegation before policy evaluation.
resp = requests.post(f"{BASE}/enforce/intercept", headers=HEADERS, json={
"action_type": "query_database",
"action_content": "SELECT * FROM trades WHERE quarter='Q1'",
"agent_id": "agent_def456",
"grant_id": "dlg_42eb33adab69",
"metadata": {"table": "trades", "operation": "read"}
})
# If grant is invalid or revoked → decision: "block"
# If grant is valid → normal policy evaluation proceeds
The principal's authority always applies (strict confused-deputy)¶
A delegate acting under a grant may never do what its principal could not. On every delegated action the Authorisation Layer evaluates the principal's own authority as well as the delegate's, and the stricter outcome stands:
- a principal that is deactivated or revoked lends no authority at all, so the delegated action is refused
- the principal's access profile is evaluated for the action, and a violation refuses or holds it exactly as it would for the principal itself
- every live rule scoped to the principal is evaluated for the action, and a rule that fires appears in the decision as
Principal rule: <name>
The result is recorded on the decision (delegation.principal, with intersection: "applied"), in the receipt's lineage, and in the plain-language explanation ("evaluated against both authorities"). If the principal's authority cannot be evaluated the action is held for a person rather than allowed. Nothing changes for actions that do not carry a grant: a principal's rules never leak onto unrelated agents.
POST /v1/enforce/delegate/{grant_id}/revoke¶
Revoke a grant with cascading, all child grants in the delegation chain are revoked automatically.
resp = requests.post(f"{BASE}/enforce/delegate/dlg_42eb33adab69/revoke",
headers=HEADERS, json={"reason": "task_complete"})
# resp.json()["revoked_count"] → 1
# resp.json()["revoked_grants"] → ["dlg_42eb33adab69"]
GET /v1/enforce/delegations¶
List all delegation grants for the workspace. Filter by status (active, revoked, expired) or agent_id.
Delegation Policies¶
Create enforcement policies of type delegation to control A2A behaviour at the workspace level.