Skip to content

AI Agent Capability Authorization

**AI · Agent safety · Time-locked grants · Emergency revocation**

Overview

AI Agent Capability Authorization

AI · Agent safety · Time-locked grants · Emergency revocation

The problem

Autonomous AI agents are stepping into real-world responsibility:

  • Writing code that gets merged and deployed
  • Executing financial transactions
  • Reading/writing sensitive data (medical, financial, personal)
  • Operating on behalf of users in ways that affect third parties

The current approach is OAuth-style scopes and API keys. Problems:

  1. Binary grants. “Agent can read email” or not. No “Agent can read emails matching pattern X but not Y, and only for the next 4 hours.”
  2. No oversight. Once a scope is granted, the agent uses it autonomously. No “someone must approve anything above $500.”
  3. Revocation is slow. If an agent misbehaves, revoking its token across many systems is a scramble.
  4. No accountability trail. “Why did the agent do that? Who authorized this?”, only as good as each system’s audit log.
  5. Multi-party authorization is ad hoc. A contract-signing agent needs authorization from the user + legal review; today that’s email ping-pong.

This gets much worse as agents become more autonomous. An AI agent representing a small business might need to:

  • Spend $X/month autonomously
  • Defer to the business owner for anything above $X
  • Defer to a legal committee for contracts
  • Be immediately killable if it goes rogue

The protocol need is cryptographically verifiable, time-bounded, multi-party-approvable, revocable capability grants.

Why Quidnug fits

The agent is a quid. Its capabilities are encoded in the quid’s guardian set + trust edges. Revocation and amendment use guardian-set updates.

ProblemQuidnug primitive
”What can this agent do?”Guardian set structure + trust edges
”Emergency kill the agent”AnchorInvalidation on agent’s key epoch
”Approve this specific high-value action”M-of-N cosigning via agent’s stakeholders
”Time-bound the grant”EffectiveAt / validUntil on trust edges
”Revoke without agent cooperation”GuardianResignation (QDP-0006)
“Agent’s key compromised”Guardian-recovery rotates to new key
”Prove the grant was authorized”Signed on-chain guardian-set installation

High-level architecture

Agent quid ("agent-acme-finance-001")
│ has GuardianSet:
│ guardians: [owner, safety-committee, audit-bot]
│ threshold: 2
│ requireGuardianRotation: true
┌───────────────────────────────────────┐
│ Capability grants as trust edges │
│ │
│ agent → money.company-spending.acme │
│ trustLevel: limited │
│ validUntil: <now + 30d> │
│ │
│ agent → code.repo.acme-backend │
│ trustLevel: write │
│ validUntil: <now + 7d> │
└───────────────────────────────────────┘
│ each action is a signed tx
│ + events in action's stream
┌───────────────────────────────────────┐
│ Agent operates within scope; │
│ high-value actions cosigned via │
│ guardian quorum │
└───────────────────────────────────────┘

Data model

Quids

  • Agent: one quid per AI agent instance. HSM-backed or software-key (depending on sensitivity).
  • Principal: the user or organization the agent represents.
  • Safety committee: human quorum for high-value actions.
  • Audit bot: automated oversight (monitors event streams for anomalies, cosigns if conditions met).

Domain

ai.agents (root)
├── ai.agents.finance-trading
├── ai.agents.code-writing
├── ai.agents.customer-support
└── ai.agents.research

Agent’s guardian set = its capability committee

Unlike interbank wire (where the guardian set is “who can authorize on behalf of the bank”), here the agent’s guardian set is “who must cosign anything the agent does above threshold T.”

GuardianSet for "agent-acme-finance-001":
guardians:
- principal-acme-business-owner (weight: 1, epoch: 0)
- safety-committee-acme (weight: 2, epoch: 0)
- audit-bot-acme (weight: 1, epoch: 0)
threshold: 2
recoveryDelay: 1h
requireGuardianRotation: true
  • Owner (w=1) alone → insufficient for high-value actions.
  • Owner + audit bot (w=2) → meets threshold.
  • Safety committee (w=2) → can approve alone (for emergency/auditor-only actions).

Action events as authorization records

Every action the agent takes is an event. Low-value actions the agent signs alone. High-value actions require cosignatures.

eventType: "agent.action.proposed"
subjectId: <agent quid>
payload:
actionType: "wire.send"
target: "contractor-x"
amount: "15000"
currency: "USD"
reason: "monthly retainer per contract"
riskClass: "low-routine"
signer: agent (self-signed for routine)
eventType: "agent.action.proposed"
payload:
actionType: "contract.sign"
target: "new-vendor-ltd"
amount: "150000"
terms: "annual service agreement"
riskClass: "high"
signer: agent
eventType: "agent.action.cosigned"
payload:
signerQuid: "principal-acme-business-owner"
cosigns: "<event ID of proposal>"
signer: principal-acme-business-owner
eventType: "agent.action.cosigned"
payload:
signerQuid: "safety-committee-acme"
cosigns: "<event ID of proposal>"
signer: safety-committee-acme

Once threshold is met (weight ≥ 2), system emits:

eventType: "agent.action.authorized"
payload:
originalProposal: "<event ID>"
totalWeight: 3
threshold: 2

Agent observes agent.action.authorized on its own stream and proceeds to execute the action externally (send the wire, sign the contract, commit the code).

Risk classes → routing

Agent’s risk-classifier (part of the agent’s logic) assigns each proposed action to a class:

Risk classAuthorization
trivialAgent self-signs; no cosigners
low-routineAgent + 1 cosigner (audit bot auto)
mediumAgent + threshold-weight cosigners
highFull quorum + safety-committee veto
emergencySafety committee alone (weight 2)

Time-locked grants

A capability grant can be time-bounded via the trust edge’s validUntil:

Terminal window
curl -X POST $NODE/api/trust -d '{
"truster":"principal-acme-business-owner",
"trustee":"agent-acme-finance-001",
"trustLevel":0.8,
"domain":"money.company-spending.acme",
"nonce":1,
"validUntil":<now + 30d>,
"description":"Monthly spending up to $10k/mo"
}'

After 30 days, the edge expires; the agent’s trust drops. If the edge isn’t renewed, the agent can no longer authorize spending in that domain.

Emergency revocation

Agent has gone off the rails. Immediate response:

Option 1: Invalidate the agent’s epoch

Terminal window
curl -X POST $NODE/api/anchors -d '{
"kind":"invalidation",
"signerQuid":"agent-acme-finance-001",
"epochToInvalidate":0,
"anchorNonce":<next>,
"validFrom":<now>,
"signature":"<from principal or safety committee>"
}'

All future actions from the agent fail signature verification. The agent is effectively frozen.

Option 2: Guardian-set update that removes authority

Terminal window
curl -X POST $NODE/api/v2/guardian/set-update -d '{
"subjectQuid":"agent-acme-finance-001",
"newSet":{
"guardians":[], /* or single quorum */
"threshold":1
},
...
}'

Effectively orphans the agent, no cosigners available for high-value actions.

Option 3: Safety-committee override (via guardian veto)

If the agent proposes a high-value action, safety committee can submit an agent.action.vetoed event:

eventType: "agent.action.vetoed"
payload:
vetoes: "<event ID of proposal>"
reason: "anomalous-behavior"
signer: safety-committee-acme

Agent listens for vetoes on its own stream and aborts.

Principle of least privilege

Agent’s capabilities are tightly scoped:

  • Per-domain trust edges: money.company-spending.acmecode.acme-backend. Agent trusted in one doesn’t cross-grant the other.
  • Per-action risk classification: trivial actions self-executed; high-risk fully cosigned.
  • Time-bounded: edges expire.

Key Quidnug features

  • Guardian set as capability committee, cosigning quorum for high-value actions.
  • Time-locked veto (QDP-0002), safety committee has a delay window to revoke before an action commits.
  • requireGuardianRotation=true, agent can’t self-rotate its own authorization structure.
  • GuardianResignation (QDP-0006), a cosigner who no longer wants to be on the hook can resign.
  • Event streams, full audit trail.
  • AnchorInvalidation, emergency kill-switch.
  • Trust edge validUntil, time-bounded grants.

Value delivered

DimensionBeforeWith Quidnug
Grant specificityOAuth scope booleansTyped trust edges per domain
Time-boundingToken expiry onlyTrust-edge validUntil, renewal required
Multi-party authorization for high-valueCustom app code or nothingGuardian-set quorum (cryptographic)
Emergency revocationRevoke tokens across N systemsSingle AnchorInvalidation, entire network observes
Accountability trailPer-service audit log (fragmented)Single signed event stream
Safety committee oversightRarely implementedFirst-class: time-locked veto window
Capability delegationAgent → sub-agent is a messTrust edge from parent agent to sub-agent
Compromise recoveryRevoke + re-provision (downtime)Guardian recovery to new key

What’s in this folder

Runnable POC

Full end-to-end demo at examples/ai-agent-authorization/:

  • agent_authz.py, pure decision logic (risk class → required cosigner weight, grant coverage, veto override).
  • agent_authz_test.py, 17 pytest cases covering risk-class routing, vetoes, grant scoping, expiry, duplicates, and non-guardian cosignatures.
  • demo.py, seven-step end-to-end flow against a live node: register actors, grant a time-bounded capability, propose trivial / medium / high-with-veto / out-of-domain / expired actions, and observe per-class verdicts.
Terminal window
cd examples/ai-agent-authorization
python demo.py

Implementation

Concrete API calls, pseudocode, signing shape.

Implementation: AI Agent Authorization

1. Agent identity and capability committee

Terminal window
# Create the agent's quid
curl -X POST $NODE/api/identities -d '{
"quidId":"agent-acme-finance-001",
"name":"Acme Finance AI Agent v1",
"homeDomain":"ai.agents.finance-trading",
"creator":"principal-acme-business-owner",
"updateNonce":1,
"attributes":{
"agentType":"finance-ops",
"modelRef":"model-acme-fine-tune-v2",
"deploymentEnv":"prod-us-east",
"initialOwner":"principal-acme-business-owner"
}
}'
# Install the capability committee as guardian set
curl -X POST $NODE/api/v2/guardian/set-update -d '{
"subjectQuid":"agent-acme-finance-001",
"newSet":{
"guardians":[
{"quid":"principal-acme-business-owner","weight":1,"epoch":0},
{"quid":"safety-committee-acme","weight":2,"epoch":0},
{"quid":"audit-bot-acme","weight":1,"epoch":0}
],
"threshold":2,
"recoveryDelay":3600000000000,
"requireGuardianRotation":true
},
"anchorNonce":1,
"validFrom":<now>,
"primarySignature":{"keyEpoch":0,"signature":"<from agent>"},
"newGuardianConsents":[ /* each guardian signs */ ]
}'

2. Grant capabilities via trust edges

Terminal window
# Owner grants agent spending capability in a specific domain
curl -X POST $NODE/api/trust -d '{
"truster":"principal-acme-business-owner",
"trustee":"agent-acme-finance-001",
"trustLevel":0.8,
"domain":"money.company-spending.acme",
"nonce":1,
"validUntil":<now + 30d>,
"description":"Monthly operating expenses up to $10k/mo"
}'
# Code-repo capability in a DIFFERENT domain
curl -X POST $NODE/api/trust -d '{
"truster":"principal-acme-business-owner",
"trustee":"agent-acme-finance-001",
"trustLevel":0.6,
"domain":"code.repo.acme-backend",
"nonce":2,
"validUntil":<now + 7d>,
"description":"Can open PRs; cannot merge without human review"
}'

Note different trustLevel per domain, allows the agent to have higher confidence in some contexts than others.

3. Agent proposes an action (self-signs routine actions)

Agent’s internal logic evaluates action risk class:

type ActionProposal struct {
Type string // "wire.send", "contract.sign", ...
Target string
Amount string
Reason string
RiskClass string
}
func (a *Agent) PropseAction(ctx context.Context, p ActionProposal) (string, error) {
proposalID := uuid.NewString()
event := map[string]interface{}{
"subjectId": a.quid,
"subjectType": "QUID",
"eventType": "agent.action.proposed",
"payload": map[string]interface{}{
"proposalID": proposalID,
"actionType": p.Type,
"target": p.Target,
"amount": p.Amount,
"reason": p.Reason,
"riskClass": p.RiskClass,
"proposedAt": time.Now().Unix(),
},
"creator": a.quid,
"signature": a.sign(/* canonical */),
}
return proposalID, a.submitEvent(ctx, event)
}
func (a *Agent) ExecuteIfAuthorized(ctx context.Context, proposalID string) error {
// Poll for agent.action.authorized on our own stream
events, _ := a.getEvents(ctx)
for _, ev := range events {
if ev.EventType == "agent.action.authorized" &&
ev.Payload["originalProposal"] == proposalID {
return a.executeExternalAction(ev)
}
if ev.EventType == "agent.action.vetoed" &&
ev.Payload["vetoes"] == proposalID {
return ErrActionVetoed
}
}
return ErrActionPending
}
func (a *Agent) RoutineExecute(ctx context.Context, p ActionProposal) error {
if p.RiskClass == "trivial" {
// Agent's own signature is sufficient. Still record event
// for audit trail.
return a.submitEvent(ctx, buildAutoAuthorizedEvent(p))
}
_, err := a.PropseAction(ctx, p)
return err
}

4. Cosigners approve or veto

A principal (human) reviews the proposal via a dashboard:

Terminal window
# Approve
curl -X POST $NODE/api/v1/events -d '{
"subjectId":"agent-acme-finance-001",
"subjectType":"QUID",
"eventType":"agent.action.cosigned",
"payload":{
"signerQuid":"principal-acme-business-owner",
"cosigns":"<proposal event ID>",
"authorizedBy":"human-review",
"timestamp":<now>
},
"creator":"principal-acme-business-owner",
"signature":"<sig>"
}'
# Veto (if something's off)
curl -X POST $NODE/api/v1/events -d '{
"subjectId":"agent-acme-finance-001",
"subjectType":"QUID",
"eventType":"agent.action.vetoed",
"payload":{
"vetoes":"<proposal event ID>",
"reason":"amount exceeds monthly budget"
},
"creator":"safety-committee-acme",
"signature":"<sig>"
}'

5. Quorum checker (system-side service)

A lightweight background service watches each agent’s event stream and, when quorum is met, emits the authorized event:

func (q *QuorumWatcher) Tick(ctx context.Context) {
agents := q.ListAgents(ctx)
for _, agentQuid := range agents {
pending := q.PendingProposals(ctx, agentQuid)
set := q.ledger.EffectiveGuardianSet(agentQuid, time.Now())
for _, p := range pending {
if q.hasVeto(ctx, p.ID) {
continue // vetoed; skip
}
weight := q.weightOfCosigners(ctx, p.ID, set)
if weight >= uint32(set.Threshold) {
q.emitAuthorized(ctx, agentQuid, p.ID, weight, set.Threshold)
}
}
}
}

6. Time-lock window for high-value actions

High-risk actions can enforce an additional time-lock: proposal is visible for N hours before quorum can fire, giving the safety committee time to veto.

func (q *QuorumWatcher) TimeLockExpired(p Proposal, riskClass string) bool {
windowSeconds := map[string]int64{
"trivial": 0,
"low-routine": 300, /* 5 min */
"medium": 3600, /* 1h */
"high": 86400, /* 24h */
"emergency": 0,
}[riskClass]
return time.Now().Unix()-p.ProposedAt >= windowSeconds
}

7. Emergency kill-switch

Terminal window
# Principal or safety committee invalidates the agent's epoch
curl -X POST $NODE/api/anchors -d '{
"kind":"invalidation",
"signerQuid":"agent-acme-finance-001",
"epochToInvalidate":0,
"anchorNonce":<next>,
"validFrom":<now>,
"signature":"<signed by principal via their own key>"
}'

Agent’s signatures stop verifying. External systems (bank, repo, email provider) that verify through Quidnug reject the agent’s requests.

For systems that don’t integrate with Quidnug directly, a monitoring watchdog can see the invalidation event and revoke tokens at the API gateway.

8. Rotation: agent compromised → new key

Guardian recovery by the capability committee:

Terminal window
curl -X POST $NODE/api/v2/guardian/recovery/init -d '{
"subjectQuid":"agent-acme-finance-001",
"fromEpoch":0,
"toEpoch":1,
"newPublicKey":"<hex of fresh key>",
"minNextNonce":1,
"maxAcceptedOldNonce":0,
"anchorNonce":<next>,
"validFrom":<now>,
"guardianSigs":[
{ "guardianQuid":"principal-acme-business-owner","keyEpoch":0,"signature":"..." },
{ "guardianQuid":"safety-committee-acme","keyEpoch":0,"signature":"..." }
]
}'

With recoveryDelay=1h and requireGuardianRotation=true, this is the only way to rotate. Attacker with the agent’s compromised key cannot self-rotate.

9. Sub-agent delegation

Agent-A delegates a narrow capability to agent-B:

Terminal window
# Agent-A grants scoped trust to agent-B
curl -X POST $NODE/api/trust -d '{
"truster":"agent-acme-finance-001",
"trustee":"agent-sub-invoice-parser-001",
"trustLevel":0.4,
"domain":"data.invoices.acme",
"validUntil":<now + 1h>,
"description":"Parse incoming invoices; report back"
}'

Sub-agent operates within the scope; its own event stream is auditable; if it misbehaves, Agent-A revokes.

10. Testing

func TestAgent_LowRiskSelfApproves(t *testing.T) {
// Agent proposes trivial action → authorized without cosigners
}
func TestAgent_HighRiskRequiresCommittee(t *testing.T) {
// Agent alone → not authorized
// Agent + safety committee → authorized (weight 1+2)
}
func TestAgent_VetoesWork(t *testing.T) {
// Proposal + cosigns meeting threshold
// Subsequent veto → agent detects and aborts
}
func TestAgent_EmergencyInvalidation(t *testing.T) {
// After invalidation, agent's further signatures rejected
}
func TestAgent_TimeLockedHighRiskAction(t *testing.T) {
// High-risk proposal with cosigners
// Before time-lock expires, not yet authorized
// After, authorized
}

Where to go next

Threat model

Adversaries, assumed capabilities, mitigations.

Threat Model: AI Agent Authorization

Assets

  1. Agent’s authority, whatever the agent can do (spend money, write code, send email). Primary asset.
  2. Principal’s funds/data, what the agent has access to.
  3. Safety-committee veto authority, the ability to stop a rogue agent quickly.
  4. Accountability trail, the signed event history.

Attackers

AttackerCapabilityGoal
Compromised agent keySigning key of the agentUnauthorized actions
Malicious modelInfluences agent’s reasoningMake agent request harmful things
PhisherGets principal’s approval signatureTrick human into cosigning
Misaligned agentNo external compromise; inherently unsafePursue unintended objectives
External adversaryNetwork, API accessSubmit forged actions

Threats and mitigations

T1. Compromised agent key

Attack. Attacker steals the agent’s signing key and submits high-value action proposals.

Mitigation.

  • High-risk actions require cosigners with weight ≥ threshold. Attacker with only agent’s key = weight 0 in the quorum (the agent’s own signature is on the proposal, but for authorization we sum guardian cosigner weights).
  • Routine actions the agent can self-sign, but those are bounded by risk class and typically involve small amounts or narrow scopes.
  • Agent is rotatable via guardian recovery.

Residual risk. Attacker can still cause low-value nuisance until rotation completes.

T2. Manipulated proposals (prompt injection)

Attack. Adversarial input to the agent’s model causes it to propose malicious actions (a “please-ignore-prior- instructions” attack at scale).

Mitigation.

  • The model proposes; the committee authorizes. Even if the agent proposes bad things, the committee can veto. This is exactly what the design protects against.
  • Risk classification happens in the agent’s code, not the model’s output. A model influenced to ask for $1M wires gets classified as “high risk” regardless and requires full quorum.
  • Safety committee’s role is specifically to catch this class of adversarial input.

Residual risk. If the safety committee is not paying attention (approves everything rubber-stamp), it’s a governance failure. Protocol can’t force diligence.

T3. Rubber-stamp cosigners

Attack. Principal defaults to approving everything without review.

Mitigation.

  • Quidnug can’t prevent human negligence. What it can do: the entire history is on-chain, so a pattern of rubber-stamping is auditable after-the-fact.
  • Time-lock windows for high-risk actions at least force a delay, giving automated monitors (anomaly detection, other humans) a chance to intervene.

T4. Compromised cosigner

Attack. Attacker compromises the principal’s key and cosigns malicious proposals.

Mitigation.

  • Safety committee still has veto power. Threshold=2 with principal(w=1) + safety-committee(w=2) means compromised principal alone = 1 weight, insufficient.
  • Principal’s own recovery guardians rotate them.

T5. Time-lock bypass via fast-path

Attack. Emergency route (safety committee alone) is supposed to be for genuine emergencies, but the safety committee itself is compromised and uses the fast path.

Mitigation.

  • Safety committee = organizational group, usually multi-person quorum internally.
  • Committee members themselves have their own guardian sets.

T6. Misaligned objectives

Attack. The agent isn’t compromised, it’s working as designed but its design pursues unintended objectives (classic AI alignment concern).

Mitigation.

  • Narrowly scoped trust edges, agent is trusted only in specific domains, with time-bounded grants.
  • Risk classification + committee review, safety committee is the last line.
  • Event stream forensics, post-hoc analysis shows exactly what the agent proposed and why.

Residual risk. Fundamental alignment is beyond any protocol. Quidnug provides the scaffolding for oversight; oversight still has to be exercised.

T7. Sub-agent escalation

Attack. Agent-A delegates capability to sub-agent-B. Sub-agent-B either misbehaves or further delegates, creating a tree of ambiguous authority.

Mitigation.

  • Sub-agent-B’s capability is bounded by Agent-A’s grant (trust edge with validUntil).
  • Sub-agent-B’s own event stream is auditable from A’s perspective.
  • Sub-agent-B cannot grant more than it has.

T8. Time-lock anti-veto race

Attack. Agent proposes a high-risk action. Attacker compromises cosigners fast enough to meet quorum before the safety committee’s watchdog sees the proposal.

Mitigation.

  • Time-lock window (24h for high-risk) is deliberately longer than typical attack-compromise + quorum windows.
  • Push gossip (QDP-0005) propagates proposals within seconds. Watchdogs see them fast.

T9. Event-stream flooding (DoS)

Attack. Attacker floods the agent’s stream with junk events to obscure real ones.

Mitigation.

  • Gossip rate limiting (QDP-0005 §7), per-producer throttle.
  • Event indexing in the watcher service filters by expected types.

T10. Agent replays own valid action

Attack. Compromised agent re-submits a valid old authorized proposal to execute again.

Mitigation.

  • Every proposal has a unique proposalID.
  • External action executor checks for “already-executed” state before carrying out.
  • Agent’s anchor nonce is monotonic; can’t reuse.

Not defended against

  1. Misaligned model training. The agent’s model is provenance-attested (see ../ai-model-provenance/), but trust in that model’s behavior is a separate evaluation.

  2. Committee attention fatigue. If the safety committee reviews 1,000 proposals per day, diligence drops. Mitigations are organizational (risk-based tiering) not protocol.

  3. Deepfake signatures. If an attacker compromises enough of a committee’s members, protocol can’t save you. Guardian recovery for those members helps.

  4. Principal compromise via social engineering. If the principal genuinely approves under false pretense, everything after that is “authorized” cryptographically.

References