Skip to content

Delegation Quickstart

Delegation lets one agent grant another agent a subset of its authority. The grant is cryptographic - a signed statement saying "I authorize agent X to do Y, subject to constraints Z." Constraints (called caveats) can only narrow authority, never widen it. Verification walks the entire chain back to the root, checking every signature and every caveat. No server calls needed.

This guide covers: create a root delegation, add caveats, build a chain, create an invocation, verify the chain, and see caveat enforcement block unauthorized actions.

Prerequisites

This guide assumes you have completed the Agent Identity Quickstart and have kanoniv-agent-auth installed.

1. Create a root delegation

A root delegation is the starting point of every authority chain. A root authority (typically representing a human or system owner) delegates to an agent with specific caveats.

rust
use kanoniv_agent_auth::{AgentKeyPair, Delegation, Caveat};

fn main() {
    // The root authority (human or system owner)
    let root = AgentKeyPair::generate();

    // The agent receiving authority
    let agent = AgentKeyPair::generate();

    // Root delegates to agent: can only resolve and search
    let delegation = Delegation::create_root(
        &root,
        &agent.identity().did,
        vec![
            Caveat::ActionScope(vec!["resolve".into(), "search".into()]),
            Caveat::MaxCost(10.0),
        ],
    ).unwrap();

    println!("Root DID: {}", delegation.issuer_did);
    println!("Agent DID: {}", delegation.delegate_did);
    println!("Chain depth: {}", delegation.depth());
    // Output: Chain depth: 0 (root delegation)
}
typescript
import { generateKeyPair, createRootDelegation } from "@kanoniv/agent-auth";

// The root authority (human or system owner)
const root = generateKeyPair();

// The agent receiving authority
const agent = generateKeyPair();

// Root delegates to agent: can only resolve and search
const delegation = createRootDelegation(root, agent.identity.did, [
  { type: "action_scope", value: ["resolve", "search"] },
  { type: "max_cost", value: 10.0 },
]);

console.log("Root DID:", delegation.issuer_did);
console.log("Agent DID:", delegation.delegate_did);
console.log("Chain depth:", delegation.parent_proof === null ? 0 : 1);
// Output: Chain depth: 0 (root delegation)
python
import json
from kanoniv_agent_auth import AgentKeyPair, Delegation

# The root authority (human or system owner)
root = AgentKeyPair.generate()

# The agent receiving authority
agent = AgentKeyPair.generate()

# Root delegates to agent: can only resolve and search
caveats = json.dumps([
    {"type": "action_scope", "value": ["resolve", "search"]},
    {"type": "max_cost", "value": 10.0},
])

delegation = Delegation.create_root(root, agent.identity().did, caveats)

print(f"Root DID: {delegation.issuer_did}")
print(f"Agent DID: {delegation.delegate_did}")
print(f"Chain depth: {delegation.depth}")
# Output: Chain depth: 0 (root delegation)

2. Agent invokes the delegated authority

When an agent needs to perform an action, it creates an invocation - a signed proof that it has the authority to do so. The invocation includes the action name, the arguments, and the entire delegation chain.

rust
use kanoniv_agent_auth::{AgentKeyPair, Delegation, Invocation, Caveat, verify_invocation};

fn main() {
    let root = AgentKeyPair::generate();
    let agent = AgentKeyPair::generate();

    let delegation = Delegation::create_root(
        &root,
        &agent.identity().did,
        vec![Caveat::ActionScope(vec!["resolve".into()])],
    ).unwrap();

    // Agent creates an invocation to exercise its delegated power
    let invocation = Invocation::create(
        &agent,
        "resolve",
        serde_json::json!({"source": "crm", "external_id": "contact-42"}),
        delegation,
    ).unwrap();

    // Anyone can verify the full chain (no server calls)
    let result = verify_invocation(
        &invocation,
        &agent.identity(),
        &root.identity(),
    ).unwrap();

    println!("Invoker: {}", result.invoker_did);
    println!("Root: {}", result.root_did);
    println!("Chain depth: {}", result.depth);
    println!("Chain: {:?}", result.chain);
}
typescript
import {
  generateKeyPair,
  createRootDelegation,
  createInvocation,
  verifyInvocation,
} from "@kanoniv/agent-auth";

const root = generateKeyPair();
const agent = generateKeyPair();

const delegation = createRootDelegation(root, agent.identity.did, [
  { type: "action_scope", value: ["resolve"] },
]);

// Agent creates an invocation to exercise its delegated power
const invocation = createInvocation(
  agent,
  "resolve",
  { source: "crm", external_id: "contact-42" },
  delegation,
);

// Anyone can verify the full chain (no server calls)
const result = verifyInvocation(invocation, agent.identity, root.identity);

console.log("Invoker:", result.invoker_did);
console.log("Root:", result.root_did);
console.log("Chain depth:", result.depth);
console.log("Chain:", result.chain);
python
import json
from kanoniv_agent_auth import AgentKeyPair, Delegation, Invocation, verify_invocation

root = AgentKeyPair.generate()
agent = AgentKeyPair.generate()

caveats = json.dumps([{"type": "action_scope", "value": ["resolve"]}])
delegation = Delegation.create_root(root, agent.identity().did, caveats)

# Agent creates an invocation to exercise its delegated power
args = json.dumps({"source": "crm", "external_id": "contact-42"})
invocation = Invocation.create(agent, "resolve", args, delegation)

# Anyone can verify the full chain (no server calls)
invoker_did, root_did, chain, depth = verify_invocation(
    invocation,
    agent.identity(),
    root.identity(),
)

print(f"Invoker: {invoker_did}")
print(f"Root: {root_did}")
print(f"Chain depth: {depth}")
print(f"Chain: {chain}")

3. Caveats block unauthorized actions

Caveats are the power of the delegation model. They are checked during verification - if any caveat is violated, verification fails. Here is an agent trying to perform an action outside its scope.

rust
use kanoniv_agent_auth::{AgentKeyPair, Delegation, Invocation, Caveat, verify_invocation};

fn main() {
    let root = AgentKeyPair::generate();
    let agent = AgentKeyPair::generate();

    // Agent is only allowed to resolve
    let delegation = Delegation::create_root(
        &root,
        &agent.identity().did,
        vec![Caveat::ActionScope(vec!["resolve".into()])],
    ).unwrap();

    // Agent tries to merge - outside its scope
    let invocation = Invocation::create(
        &agent,
        "merge",
        serde_json::json!({"entity_ids": ["a", "b"]}),
        delegation,
    ).unwrap();

    match verify_invocation(&invocation, &agent.identity(), &root.identity()) {
        Ok(_) => println!("This should not happen"),
        Err(e) => println!("Blocked: {}", e),
        // Output: Blocked: caveat violation: action 'merge' not in allowed scope ["resolve"]
    }
}
typescript
import {
  generateKeyPair,
  createRootDelegation,
  createInvocation,
  verifyInvocation,
} from "@kanoniv/agent-auth";

const root = generateKeyPair();
const agent = generateKeyPair();

// Agent is only allowed to resolve
const delegation = createRootDelegation(root, agent.identity.did, [
  { type: "action_scope", value: ["resolve"] },
]);

// Agent tries to merge - outside its scope
const invocation = createInvocation(
  agent,
  "merge",
  { entity_ids: ["a", "b"] },
  delegation,
);

try {
  verifyInvocation(invocation, agent.identity, root.identity);
  console.log("This should not happen");
} catch (e) {
  console.log("Blocked:", (e as Error).message);
  // Output: Blocked: Caveat violation: action 'merge' not in allowed scope
}
python
import json
from kanoniv_agent_auth import AgentKeyPair, Delegation, Invocation, verify_invocation

root = AgentKeyPair.generate()
agent = AgentKeyPair.generate()

# Agent is only allowed to resolve
caveats = json.dumps([{"type": "action_scope", "value": ["resolve"]}])
delegation = Delegation.create_root(root, agent.identity().did, caveats)

# Agent tries to merge - outside its scope
args = json.dumps({"entity_ids": ["a", "b"]})
invocation = Invocation.create(agent, "merge", args, delegation)

try:
    verify_invocation(invocation, agent.identity(), root.identity())
    print("This should not happen")
except ValueError as e:
    print(f"Blocked: {e}")
    # Output: Blocked: caveat violation: action 'merge' not in allowed scope ["resolve"]

4. Build a delegation chain

Authority can flow through multiple agents. Each step can add restrictions but never remove them. A manager delegates to a worker with a narrower scope - the worker can only do a subset of what the manager can do.

rust
use kanoniv_agent_auth::{AgentKeyPair, Delegation, Invocation, Caveat, verify_invocation};

fn main() {
    let root = AgentKeyPair::generate();
    let manager = AgentKeyPair::generate();
    let worker = AgentKeyPair::generate();

    // Root gives manager: resolve, search, merge
    let d1 = Delegation::create_root(
        &root,
        &manager.identity().did,
        vec![Caveat::ActionScope(vec![
            "resolve".into(),
            "search".into(),
            "merge".into(),
        ])],
    ).unwrap();

    // Manager gives worker: only resolve (narrower)
    let d2 = Delegation::delegate(
        &manager,
        &worker.identity().did,
        vec![Caveat::ActionScope(vec!["resolve".into()])],
        d1,
    ).unwrap();

    println!("Chain depth: {}", d2.depth());
    // Output: Chain depth: 1

    // Worker resolves - allowed
    let inv_ok = Invocation::create(
        &worker,
        "resolve",
        serde_json::json!({"source": "crm"}),
        d2.clone(),
    ).unwrap();

    let result = verify_invocation(&inv_ok, &worker.identity(), &root.identity()).unwrap();
    println!("Resolve allowed, depth: {}", result.depth);
    // Output: Resolve allowed, depth: 2

    // Worker tries to search - blocked by worker's narrower scope
    let inv_bad = Invocation::create(
        &worker,
        "search",
        serde_json::json!({"query": "John"}),
        d2,
    ).unwrap();

    match verify_invocation(&inv_bad, &worker.identity(), &root.identity()) {
        Err(e) => println!("Search blocked: {}", e),
        Ok(_) => println!("This should not happen"),
    }
}
typescript
import {
  generateKeyPair,
  createRootDelegation,
  delegateAuthority,
  createInvocation,
  verifyInvocation,
} from "@kanoniv/agent-auth";

const root = generateKeyPair();
const manager = generateKeyPair();
const worker = generateKeyPair();

// Root gives manager: resolve, search, merge
const d1 = createRootDelegation(root, manager.identity.did, [
  { type: "action_scope", value: ["resolve", "search", "merge"] },
]);

// Manager gives worker: only resolve (narrower)
const d2 = delegateAuthority(manager, worker.identity.did, [
  { type: "action_scope", value: ["resolve"] },
], d1);

// Worker resolves - allowed
const invOk = createInvocation(
  worker,
  "resolve",
  { source: "crm" },
  d2,
);

const result = verifyInvocation(invOk, worker.identity, root.identity);
console.log("Resolve allowed, depth:", result.depth);
// Output: Resolve allowed, depth: 2

// Worker tries to search - blocked by worker's narrower scope
const invBad = createInvocation(
  worker,
  "search",
  { query: "John" },
  d2,
);

try {
  verifyInvocation(invBad, worker.identity, root.identity);
  console.log("This should not happen");
} catch (e) {
  console.log("Search blocked:", (e as Error).message);
}
python
import json
from kanoniv_agent_auth import AgentKeyPair, Delegation, Invocation, verify_invocation

root = AgentKeyPair.generate()
manager = AgentKeyPair.generate()
worker = AgentKeyPair.generate()

# Root gives manager: resolve, search, merge
caveats_mgr = json.dumps([
    {"type": "action_scope", "value": ["resolve", "search", "merge"]}
])
d1 = Delegation.create_root(root, manager.identity().did, caveats_mgr)

# Manager gives worker: only resolve (narrower)
caveats_wkr = json.dumps([
    {"type": "action_scope", "value": ["resolve"]}
])
d2 = Delegation.delegate(manager, worker.identity().did, caveats_wkr, d1)

print(f"Chain depth: {d2.depth}")
# Output: Chain depth: 1

# Worker resolves - allowed
inv_ok = Invocation.create(
    worker,
    "resolve",
    json.dumps({"source": "crm"}),
    d2,
)

invoker_did, root_did, chain, depth = verify_invocation(
    inv_ok,
    worker.identity(),
    root.identity(),
)
print(f"Resolve allowed, depth: {depth}")
# Output: Resolve allowed, depth: 2

# Worker tries to search - blocked by worker's narrower scope
inv_bad = Invocation.create(
    worker,
    "search",
    json.dumps({"query": "John"}),
    d2,
)

try:
    verify_invocation(inv_bad, worker.identity(), root.identity())
    print("This should not happen")
except ValueError as e:
    print(f"Search blocked: {e}")

Caveat types

Six built-in caveat types cover the most common authorization patterns:

CaveatDescriptionExample
action_scopeRestrict to specific actions["resolve", "search"]
expires_atDelegation expires at an RFC 3339 timestamp"2026-12-31T23:59:59.000Z"
max_costCost ceiling - requires cost field in args5.0
resourceGlob pattern on a resource field in args"entity:customer:*"
contextExact match on a key/value pair in args{"key": "session_id", "value": "sess-abc"}
customArbitrary key/value constraint{"key": "org", "value": "acme"}

Caveats accumulate through the chain. If root says action_scope: [resolve, search] and a middle agent adds action_scope: [resolve], the leaf agent can only resolve. You cannot add a caveat that widens what a parent granted.

Security properties

  • Every signature in the chain is verified. Not just the root and leaf - every intermediate delegation is checked against its issuer's embedded public key.
  • Caveats are read from the signed payload, not from outer (tamperable) fields. If someone modifies the caveats on the Delegation object, verification reads the original caveats from inside the cryptographic envelope and enforces those.
  • Chain depth is capped at 32 to prevent denial-of-service via deeply nested chains.
  • No external lookups. Public keys are embedded in each delegation, so the full chain can be verified offline.

Next steps

The identity and delegation layer for AI agents.