Delegation
Delegation is how authority flows between agents in Kanoniv. A root authority grants an agent permission to act on its behalf, and that agent can sub-delegate to other agents - each step narrowing what the downstream agent can do. Every delegation is cryptographically signed and verifiable without server calls.
Why Not Just API Keys?
API keys are all-or-nothing. An agent with a key can do everything the key permits. There is no way to express "resolve only, for customer entities, until 5pm." If you spin up a sub-agent to handle a specific task, you either give it your full key or build a bespoke proxy.
OAuth scopes are better but still centralized. The authorization server must be online to issue and validate tokens. In multi-agent systems where agents spawn sub-agents dynamically, every scope change requires a round-trip to the auth server.
Delegation solves both problems:
- Fine-grained: Caveats restrict what, where, how much, and how long.
- Decentralized: Verification uses embedded public keys and signatures. No server calls.
- Composable: Any agent can sub-delegate to another, adding restrictions but never removing them.
The Attenuation Model
Kanoniv delegation follows a Macaroon-style attenuation model. The key insight: caveats can only narrow authority, never widen it.
Root Authority
|
| Delegation: [resolve, search, merge] expires 2026-04-01
v
Manager Agent
|
| Sub-delegation: [resolve, search] expires 2026-04-01, resource: entity:customer:*
v
Worker Agent
|
| Sub-delegation: [resolve] expires 2026-03-20, resource: entity:customer:*, max_cost: 1.0
v
Specialized AgentAt each step, the authority gets smaller. The worker agent above can only resolve customer entities, at a cost of at most $1.00, until March 20. It cannot search, merge, or access non-customer entities - even though the root delegation allowed all of those.
This is the opposite of privilege escalation. An agent can never grant more than it was given.
How It Works
Three primitives make up the delegation system:
1. Delegation
A signed statement: "I (issuer) grant you (delegate) authority X with constraints Y."
{
"issuer_did": "did:agent:a1b2c3...",
"delegate_did": "did:agent:d4e5f6...",
"caveats": [
{ "type": "action_scope", "value": ["resolve", "search"] },
{ "type": "expires_at", "value": "2026-04-01T00:00:00.000Z" }
],
"proof": { "...signed envelope..." }
}2. Invocation
A signed action: "I am exercising authority X. Here is my proof."
{
"invoker_did": "did:agent:d4e5f6...",
"action": "resolve",
"args": { "entity_id": "customer-123" },
"delegation": { "...the delegation chain..." },
"proof": { "...signed envelope..." }
}3. Verification
Walk the chain from invoker back to root. Every signature is checked. Every caveat is enforced. If anything fails, the invocation is rejected. No partial passes.
Authorization Requires Identity
You cannot authorize an agent to do something if you cannot verify who it is. API keys identify a tenant, not an agent. OAuth tokens identify a user session, not a specific AI agent instance. Kanoniv delegation builds on did:agent: identifiers - Ed25519 keypairs where the DID is derived from the public key hash. The agent proves its identity by signing with its private key. The delegation proves its authority by chaining signatures back to a trusted root. Identity first. Delegation second. Execution last.
Quick Example
use kanoniv_agent_auth::{AgentKeyPair, Delegation, Invocation, Caveat, verify_invocation};
let root = AgentKeyPair::generate();
let worker = AgentKeyPair::generate();
let delegation = Delegation::create_root(&root, &worker.identity().did, vec![
Caveat::ActionScope(vec!["resolve".into(), "search".into()]),
Caveat::ExpiresAt("2026-03-16T00:00:00.000Z".into()),
]).unwrap();
let invocation = Invocation::create(
&worker, "resolve", serde_json::json!({"entity_id": "cust-42"}), delegation,
).unwrap();
let result = verify_invocation(&invocation, &worker.identity(), &root.identity()).unwrap();import { generateKeyPair, createRootDelegation, createInvocation, verifyInvocation } from "@kanoniv/agent-auth";
const root = generateKeyPair();
const worker = generateKeyPair();
const delegation = createRootDelegation(root, worker.identity.did, [
{ type: "action_scope", value: ["resolve", "search"] },
{ type: "expires_at", value: "2026-03-16T00:00:00.000Z" },
]);
const invocation = createInvocation(worker, "resolve", { entity_id: "cust-42" }, delegation);
const result = verifyInvocation(invocation, worker.identity, root.identity);from kanoniv_agent_auth import AgentKeyPair, Delegation, Invocation, verify_invocation
import json
root = AgentKeyPair.generate()
worker = AgentKeyPair.generate()
delegation = Delegation.create_root(root, worker.identity().did, json.dumps([
{"type": "action_scope", "value": ["resolve", "search"]},
{"type": "expires_at", "value": "2026-03-16T00:00:00.000Z"},
]))
invocation = Invocation.create(worker, "resolve", json.dumps({"entity_id": "cust-42"}), delegation)
invoker_did, root_did, chain, depth = verify_invocation(invocation, worker.identity(), root.identity())Next Steps
- Delegation Chains - Root vs chained delegations, parent_proof linkage, multi-level examples
- Caveats - All 6 caveat types with recipes and accumulation semantics
- Verification - The 8-step verification process, fail-closed semantics, error types
- Revocation - The is_revoked callback, content hash identifiers, implementation patterns
