Permission Model for Multi-Agent Systems
TL;DR
What is Authorization? An OS-level primitive separating permission ("may do") from capability ("can do").
Key insight: Trust Assertion Protocol (TAP) proves an agent can perform a task. Authorization proves an agent may perform it with defined scope and constraints.
The primitive:
AuthorizationGrant(
principal, # Who grants permission
agent, # Who receives permission
scope, # What actions allowed
constraints, # Budgets, limits, restrictions
valid_until # Temporal bounds
)
Why it matters: Multi-agent coordination requires explicit permission models. Capability ≠ authority.
The Problem
Traditional systems conflate capability (what you can do) with authorization (what you may do):
Without separation:
# Agent has deployment capability via TAP
if has_capability(agent, "deploy-production"):
deploy() # ❌ No permission check!
Problems:
- Agent with capability auto-granted permission (security risk)
- No scope limits (can deploy anything, anywhere)
- No budget constraints (unlimited resource consumption)
- No temporal bounds (permission never expires)
- No audit trail of grants (compliance failure)
- Cannot revoke permission without removing capability (inflexible)
The Solution: Authorization as Separate Primitive
Authorization extends TAP with explicit permission grants:
# Step 1: Check capability (TAP)
tap = query_tap(agent, "has-capability", "deploy-production")
if not tap:
return Error("Agent lacks capability")
# Step 2: Check authorization (NEW primitive)
auth = query_authorization(agent, "deploy-production")
if not auth or auth.expired():
return Error("Agent lacks permission")
# Step 3: Validate constraints
if exceeds_budget(task, auth.budget):
return Error("Exceeds authorized budget")
# All checks pass → delegate
delegate(agent, task)
OS Architecture Perspective
Every multi-agent operating system needs two permission models:
| Model | What It Proves | Semantic OS Primitive | Analogous To |
|---|---|---|---|
| Capability Model | Technical ability | TAP (Trust Assertion Protocol) | Unix: executable bit, process capabilities |
| Permission Model | Granted authority | AuthorizationGrant (this protocol) | Unix: file ownership, ACLs, sudo grants |
Traditional OS Example:
# User can execute /usr/bin/reboot (capability)
$ ls -l /usr/bin/reboot
-rwxr-xr-x 1 root root
# But needs sudo permission (authorization)
$ reboot
Permission denied
$ sudo reboot # ✓ Has both capability AND authorization
Semantic OS Example:
# Agent CAN deploy (TAP capability)
tap_assertion = { "type": "has-capability", "value": "deploy-production" }
# Agent MAY deploy (Authorization permission)
auth_grant = AuthorizationGrant(
principal="did:user:alice",
agent="did:agent:deployment-bot",
scope=["deploy-production"],
constraints={"max_instances": 10, "budget_usd": 1000},
valid_until="2025-12-31T23:59:59Z"
)
AuthorizationGrant Primitive
Schema (Layer 1: Pantheon IR)
from dataclasses import dataclass
from datetime import datetime
from typing import List, Dict, Optional
@dataclass
class AuthorizationGrant:
"""
Explicit permission to perform actions with defined scope and constraints.
Stored in GenesisGraph as typed edge: principal --[grants-auth]--> agent
Checked by Agent Ether before delegation.
"""
# Core fields
grant_id: str # Unique identifier
principal: str # DID of who grants (human or org)
agent: str # DID of who receives (agent)
scope: List[str] # What actions permitted
# Constraints
constraints: Dict[str, any] # Budgets, limits, restrictions
# Common constraints:
# budget_usd: float # Dollar spending limit
# max_api_calls: int # API call limit
# allowed_domains: List[str] # Domain restrictions
# rate_limit: str # "100/hour"
# Temporal
valid_from: datetime # When permission starts
valid_until: datetime # When permission expires
# Revocation
revocable: bool = True # Can be revoked?
revoked_at: Optional[datetime] = None
# Provenance
granted_at: datetime # When grant created
provenance_node: str # GenesisGraph node ID
# Optional
delegation_depth: int = 0 # How deep can subdelegate
requires_confirmation: bool = False # Needs user confirm per use?
Lifecycle
┌─────────────┐
│ PENDING │ Grant created, not yet active
└──────┬──────┘
│
▼
┌─────────────┐
│ ACTIVE │ valid_from ≤ now < valid_until
└──────┬──────┘
│
▼
┌─────────────┐
│ EXPIRED │ now ≥ valid_until (cannot be used)
└─────────────┘
OR
┌─────────────┐
│ REVOKED │ Principal revoked early (revoked_at set)
└─────────────┘
Integration with Semantic OS Layers
Layer 0: Semantic Memory
AuthorizationGrant stored as typed GenesisGraph edge:
(principal:DID) --[grants-authorization]--> (agent:DID)
Edge metadata:
- grant_id
- scope (list)
- constraints (dict)
- temporal bounds (valid_from, valid_until)
- revocation status
- provenance chain
Layer 1: Pantheon IR
AuthorizationGrant is a semantic type in Pantheon IR vocabulary:
- Composes with TAP assertions
- Validated by domain modules
- Lifted/lowered across representations
Layer 3: Agent Ether
Before delegation, Agent Ether checks authorization:
async def delegate_task(agent_id: str, task: Task) -> Result:
# 1. Check capability (TAP)
if not await verify_capability(agent_id, task.required_capability):
return Error("Agent lacks capability")
# 2. Check authorization (NEW)
auth = await query_authorization(agent_id, task.action)
if not auth:
return Error("No authorization grant found")
if auth.expired():
return Error("Authorization expired")
if auth.revoked_at:
return Error("Authorization was revoked")
# 3. Validate constraints
if not validate_constraints(task, auth.constraints):
return Error("Task violates authorization constraints")
# 4. Check delegation depth (if subdelegating)
if task.delegation_chain_depth > auth.delegation_depth:
return Error("Exceeds delegation depth limit")
# All checks pass
return await execute_with_provenance(agent_id, task, auth.grant_id)
Layer 5: Interfaces
User grants authorization through UI:
// User grants agent permission to deploy
const grant = await createAuthorizationGrant({
agent: "did:agent:deployment-bot",
scope: ["deploy-production", "rollback-production"],
constraints: {
budget_usd: 1000,
max_instances: 10,
allowed_regions: ["us-west-2"]
},
valid_until: "2025-12-31T23:59:59Z"
})
// Grant flows through Layer 5 → Layer 1 → Layer 0 (stored)
// Agent Ether (Layer 3) can now query it for delegation decisions
Legal/Agency Law Grounding
Authorization maps directly to agency law (Restatement Third of Agency):
| Agency Law Concept | Authorization Primitive |
|---|---|
| Principal | principal field (who grants) |
| Agent | agent field (who receives) |
| Actual Authority | AuthorizationGrant existence + validity |
| Scope of Authority | scope field (what actions) |
| Limitations | constraints field (budgets, restrictions) |
| Duration | valid_from, valid_until (temporal bounds) |
| Revocation | revoked_at field |
Legal principle: An agent acts with actual authority when, at the time of taking action, the agent reasonably believes the principal wishes the agent so to act (§2.01).
Authorization provides proof that principal granted authority, with scope, at a specific time.
Examples
Example 1: Deployment Authorization
# Principal (Alice) grants deployment agent permission
grant = AuthorizationGrant(
grant_id="auth:grant:abc123",
principal="did:user:alice",
agent="did:agent:deployment-bot",
scope=["deploy-production", "rollback-production"],
constraints={
"budget_usd": 1000.00,
"max_instances": 10,
"allowed_regions": ["us-west-2", "eu-west-1"],
"requires_approval_over": 500.00 # Dollar threshold
},
valid_from=datetime(2025, 12, 1),
valid_until=datetime(2025, 12, 31, 23, 59, 59),
delegation_depth=0, # Cannot subdelegate
granted_at=datetime(2025, 12, 1, 10, 0, 0),
provenance_node="gg:node:xyz789"
)
# Later: Agent attempts deployment
deployment_task = Task(
action="deploy-production",
params={"region": "us-west-2", "instances": 5, "estimated_cost": 450.00}
)
# Agent Ether checks authorization
auth = query_authorization("did:agent:deployment-bot", "deploy-production")
✓ Grant exists
✓ Not expired (now < valid_until)
✓ Not revoked
✓ Action in scope ("deploy-production" in grant.scope)
✓ Constraints satisfied:
- estimated_cost (450) < budget_usd (1000) ✓
- instances (5) ≤ max_instances (10) ✓
- region ("us-west-2") in allowed_regions ✓
- No approval required (450 < 500 threshold) ✓
→ Deployment authorized, proceed
Example 2: Budget Exhaustion
# Same grant, but agent has consumed budget
grant.constraints["budget_used"] = 950.00 # Track spending
deployment_task = Task(
action="deploy-production",
params={"region": "us-west-2", "instances": 3, "estimated_cost": 200.00}
)
# Check constraints
budget_remaining = grant.constraints["budget_usd"] - grant.constraints["budget_used"]
# 1000 - 950 = 50
if deployment_task.estimated_cost > budget_remaining:
return Error("Budget exhausted: $200 requested, $50 remaining")
→ Deployment blocked, escalate to principal
Example 3: Revocation
# Principal revokes authorization mid-way
revoke_authorization(grant_id="auth:grant:abc123")
# Sets grant.revoked_at = datetime.now()
# Agent attempts action
auth = query_authorization("did:agent:deployment-bot", "deploy-production")
if auth.revoked_at:
return Error(f"Authorization revoked at {auth.revoked_at}")
→ All future actions blocked
→ Agent's capability (TAP) still exists, but permission removed
Validation Rules
When creating AuthorizationGrant:
- Principal must exist (valid DID)
- Agent must exist (valid DID)
- Scope must be non-empty (at least one action)
- Temporal bounds must be valid (
valid_from < valid_until) - Constraints must be well-formed (valid JSON, types correct)
When checking authorization:
- Grant must exist for (agent, action) pair
- Must not be expired (
now < valid_until) - Must not be revoked (
revoked_at == None) - Action must be in scope (
action in grant.scope) - Constraints must be satisfied (all constraints pass)
- Delegation depth must not exceed limit (if subdelegating)
Constraint validation (type-specific):
def validate_constraints(task: Task, constraints: Dict) -> bool:
for key, limit in constraints.items():
if key == "budget_usd":
if task.estimated_cost > limit:
return False
elif key == "max_api_calls":
if task.api_calls > limit:
return False
elif key == "allowed_domains":
if task.domain not in limit:
return False
# ... other constraint types
return True
Failure Modes & Mitigations
Failure Mode 1: Overly Broad Grants
Risk: Principal grants authorization with scope="*" (all actions)
Mitigation:
- UI warns when scope > 5 actions
- Require explicit enumeration (no wildcards in v1)
- Audit trail shows broad grants for review
Failure Mode 2: Forgotten Expiration
Risk: Grant valid_until = far future (effectively permanent)
Mitigation:
- UI defaults to 30-day expiration
- Warn when valid_until > 90 days
- Periodic review reminders
Failure Mode 3: Constraint Bypass
Risk: Agent modifies task params to evade constraints
Mitigation:
- Constraint validation in Agent Ether (not agent-side)
- Provenance records original task params + constraint evaluation
- Tampering detectable in audit
Failure Mode 4: Revocation Not Honored
Risk: Agent caches authorization, ignores revocation
Mitigation:
- Agent Ether always queries GenesisGraph (no caching)
- Revocation propagates immediately
- Failed revocation check = hard stop
Relationship to Other Protocols
TAP (Trust Assertion Protocol)
TAP proves capability:
{ "type": "has-capability", "value": "deploy-production" }
Authorization proves permission:
AuthorizationGrant(scope=["deploy-production"], ...)
Both required:
if has_capability(agent, action) AND has_authorization(agent, action):
proceed()
See: TRUST_ASSERTION_PROTOCOL.md (TAP vs Authorization section)
Delegation (Hierarchical Agency)
Authorization enables delegation:
# Alice grants deployment-bot permission
alice_grant = AuthorizationGrant(
principal="did:user:alice",
agent="did:agent:deployment-bot",
delegation_depth=1 # Can subdelegate once
)
# Deployment-bot subdelegates to region-specific agent
subgrant = AuthorizationGrant(
principal="did:agent:deployment-bot", # Now principal
agent="did:agent:us-west-deployer",
scope=alice_grant.scope, # Must narrow or equal
delegation_depth=0, # Decremented (no further delegation)
derived_from=alice_grant.grant_id # Provenance link
)
See: HIERARCHICAL_AGENCY_FRAMEWORK.md (delegation constraints)
Intent Contracts
IntentContract references AuthorizationGrant:
intent = IntentContract(
goal="Deploy new feature",
authorization_id="auth:grant:abc123", # Links to grant
...
)
Authorization validates intent execution:
- Intent says "what" user wants
- Authorization says "may the agent do it"
See: INTENT_VERIFICATION_PROTOCOL.md
Implementation Guidance
Storage (GenesisGraph)
# Create grant → store as graph edge
def create_authorization_grant(grant: AuthorizationGrant):
edge = {
"type": "grants-authorization",
"from": grant.principal,
"to": grant.agent,
"metadata": {
"grant_id": grant.grant_id,
"scope": grant.scope,
"constraints": grant.constraints,
"valid_from": grant.valid_from.isoformat(),
"valid_until": grant.valid_until.isoformat(),
"revocable": grant.revocable,
"delegation_depth": grant.delegation_depth,
},
"provenance": {
"created_at": grant.granted_at.isoformat(),
"created_by": grant.principal,
}
}
genesis_graph.add_edge(edge)
return grant.grant_id
Query (Agent Ether)
# Check if agent has authorization for action
def query_authorization(agent_id: str, action: str) -> Optional[AuthorizationGrant]:
# Query GenesisGraph for edges: * --[grants-authorization]--> agent
edges = genesis_graph.query(
edge_type="grants-authorization",
to_node=agent_id
)
for edge in edges:
grant = AuthorizationGrant.from_edge(edge)
# Check validity
if grant.revoked_at:
continue # Skip revoked
now = datetime.now()
if now < grant.valid_from or now >= grant.valid_until:
continue # Skip expired/pending
# Check scope
if action in grant.scope:
return grant
return None # No valid grant found
Revocation
def revoke_authorization(grant_id: str, principal: str):
# Only principal who granted can revoke
edge = genesis_graph.get_edge_by_grant_id(grant_id)
if edge.metadata["from"] != principal:
raise PermissionError("Only granting principal can revoke")
# Mark as revoked (immutable append to graph)
edge.metadata["revoked_at"] = datetime.now().isoformat()
edge.metadata["revoked_by"] = principal
genesis_graph.update_edge(edge)
CLI Integration
# Grant authorization
$ tia auth grant \
--agent did:agent:deployment-bot \
--scope deploy-production,rollback-production \
--budget 1000 \
--expires 2025-12-31
Authorization granted: auth:grant:abc123
# List authorizations for agent
$ tia auth list --agent did:agent:deployment-bot
Grant ID: auth:grant:abc123
Agent: did:agent:deployment-bot
Scope: deploy-production, rollback-production
Constraints: budget_usd=1000, max_instances=10
Valid: 2025-12-01 to 2025-12-31
Status: ACTIVE
# Check if action authorized
$ tia auth check \
--agent did:agent:deployment-bot \
--action deploy-production
✓ Authorized (grant: auth:grant:abc123)
Budget remaining: $550 / $1000
# Revoke authorization
$ tia auth revoke auth:grant:abc123
Authorization revoked at 2025-12-15T10:30:00Z
See Also
- TRUST_ASSERTION_PROTOCOL.md — TAP vs Authorization distinction
- HIERARCHICAL_AGENCY_FRAMEWORK.md — Authority levels, delegation rules
- INTENT_VERIFICATION_PROTOCOL.md — Intent references authorization
- SIL_GLOSSARY.md — Authorization, AuthorizationGrant definitions
- SIL_SAFETY_THRESHOLDS.md — Safety constraints in authorization
Status: Specification complete (2025-12-12)
Layer: Cross-cutting (Layer 0 storage, Layer 1 semantics, Layer 3 enforcement, Layer 5 UI)
Depends on: TAP, GenesisGraph, Pantheon IR
Enables: Safe multi-agent delegation, legal defensibility, forensic accountability