Delegation chains
Grant a subset of permissions from one agent to another, with depth limits and cascading revocation.
What is a delegation chain
A delegation chain lets one agent grant a subset of its permissions to another. The delegating agent keeps its own permissions unchanged. The receiving agent gains access only to what was explicitly delegated.
This pattern is most useful when an orchestrator spins up sub-agents for specific tasks. Each sub-agent gets only the access it needs, for only as long as it needs it.
The delegating agent must currently hold every permission it is trying to delegate. Attempting to delegate a permission not in the agent's own set fails with INSUFFICIENT_PERMISSIONS.
Working with delegations
Creating a delegation
const chain = await kavach.delegate({
fromAgent: orchestrator.id,
toAgent: subAgent.id,
permissions: [
{ resource: 'mcp:github:issues', actions: ['read'] },
],
expiresAt: new Date(Date.now() + 3_600_000), // 1 hour
maxDepth: 2,
});
console.log(chain.id); // dlg_...
console.log(chain.depth); // 1
console.log(chain.expiresAt); // DateProp
Type
Permission subset enforcement
The permissions you delegate must be a subset of what the fromAgent holds. Narrower resources and fewer actions are allowed. Wider resources or new actions are rejected.
Given an orchestrator with:
{ resource: 'mcp:github:*', actions: ['read', 'write', 'comment'] }Valid delegations:
{ resource: 'mcp:github:issues', actions: ['read'] } // narrower resource, fewer actions
{ resource: 'mcp:github:*', actions: ['read'] } // same resource, fewer actions
{ resource: 'mcp:github:repos', actions: ['read', 'comment'] } // narrower resource, same actionsInvalid delegations:
{ resource: 'mcp:github:*', actions: ['delete'] } // action not held by parent
{ resource: 'mcp:slack:*', actions: ['read'] } // resource not held by parentDepth limiting
maxDepth controls how many additional re-delegation hops are allowed. With maxDepth: 2, the receiving agent can delegate to another agent, but that next agent cannot delegate further.
// Orchestrator → Sub (depth 1, maxDepth 2)
await kavach.delegate({
fromAgent: orchestrator.id,
toAgent: sub.id,
permissions: [{ resource: 'mcp:github:issues', actions: ['read'] }],
expiresAt: new Date(Date.now() + 3_600_000),
maxDepth: 2,
});
// Sub → SubSub (depth 2, maxDepth 2): allowed
await kavach.delegate({
fromAgent: sub.id,
toAgent: subSub.id,
permissions: [{ resource: 'mcp:github:issues', actions: ['read'] }],
expiresAt: new Date(Date.now() + 1_800_000),
maxDepth: 1,
});
// SubSub → anything: blocked, depth limit reachedThe default maxDepth when not specified is 3.
Cascading revocation
Revoking a chain removes it and all chains created downstream of it in the same tree.
await kavach.delegation.revoke(chain.id);If the chain is orchestrator → sub → subSub, revoking orchestrator → sub also revokes sub → subSub immediately. Any agent that relied on the revoked permissions will get allowed: false on its next authorization check.
Revocation takes effect on the next authorize() call. It does not terminate any in-progress operations.
Effective permissions
To see the full set of permissions an agent has at a given moment, including those received through active chains:
const effective = await kavach.delegation.getEffectivePermissions(subAgent.id);
// Returns Permission[] combining own permissions and all active delegationsThe authorization engine calls this internally on every authorize() request. You can call it directly to inspect what an agent can currently do before attempting an action.
Listing chains
// All chains where subAgent is the receiver
const incoming = await kavach.delegation.listChains({ toAgent: subAgent.id });
// All chains originating from the orchestrator
const outbound = await kavach.delegation.listChains({ fromAgent: orchestrator.id });DelegationChain type
Prop
Type
Typical pattern
An orchestrator holds broad permissions, plans a task, and issues short-lived narrow delegations to sub-agents.
Create the orchestrator with the full permission set it needs.
const orchestrator = await kavach.agent.create({
ownerId: 'user-123',
name: 'planner',
type: 'autonomous',
permissions: [
{ resource: 'mcp:github:*', actions: ['read', 'write', 'comment'] },
{ resource: 'mcp:linear:*', actions: ['read', 'write'] },
],
});Create the sub-agent with no direct permissions.
const codeReviewer = await kavach.agent.create({
ownerId: 'user-123',
name: 'code-reviewer',
type: 'delegated',
permissions: [],
});Delegate only what the sub-agent needs, with a short expiry and maxDepth: 1 to prevent further delegation.
await kavach.delegate({
fromAgent: orchestrator.id,
toAgent: codeReviewer.id,
permissions: [
{ resource: 'mcp:github:pulls', actions: ['read', 'comment'] },
],
expiresAt: new Date(Date.now() + 30 * 60_000), // 30 minutes
maxDepth: 1,
});