kavachOS
Core concepts

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); // Date

Prop

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 actions

Invalid delegations:

{ resource: 'mcp:github:*', actions: ['delete'] }  // action not held by parent
{ resource: 'mcp:slack:*', actions: ['read'] }     // resource not held by parent

Depth 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 reached

The 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 delegations

The 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,
});

Next steps

On this page