Implement Multi-Agent Delegation
Delegation transfers authority from one agent session to another. The Coordinator records a directed edge in the delegation graph. The STS validates the edge on every token exchange. Revoking an edge cascades automatically to all downstream agents that received authority through it.
Prerequisites
Section titled “Prerequisites”- The Caracal SDK installed in the orchestrating service (TypeScript, Python, or Go).
- The Coordinator URL reachable at
CARACAL_COORDINATOR_URL. - An active policy that allows delegation exchanges.
Spawn a root agent session
Section titled “Spawn a root agent session”Every execution begins with spawn(). The SDK opens a session on the Coordinator and binds it to the current async context:
TypeScript:
import { Caracal, AgentKind } from '@caracalai/sdk';
const caracal = Caracal.fromEnv();
await caracal.spawn(async () => { const ctx = caracal.current()!; console.log('root session:', ctx.agentSessionId); // depth = 0}, { kind: AgentKind.Instance, ttlSeconds: 3600 });Python:
from caracalai_sdk import Caracalfrom caracalai_sdk.advanced import AgentKind
caracal = Caracal.from_env()
async with caracal.spawn(kind=AgentKind.INSTANCE, ttl_seconds=3600) as ctx: print('root session:', ctx.agent_session_id)Go:
err = client.Spawn(ctx, func(ctx context.Context) error { cc, _ := sdk.Current(ctx) fmt.Printf("root session: %s\n", cc.AgentSessionID) return nil}, sdk.SpawnOptions{Kind: sdk.KindInstance, TTLSeconds: 3600})Spawn a child agent (nested spawn)
Section titled “Spawn a child agent (nested spawn)”Call spawn() again inside the first callback. The Coordinator records the parent-child relationship:
TypeScript:
await caracal.spawn(async () => { const parentCtx = caracal.current()!;
// Second spawn inside the first — becomes a child await caracal.spawn(async () => { const childCtx = caracal.current()!; console.log('child session:', childCtx.agentSessionId); // depth = 1, parent = parentCtx.agentSessionId }, { kind: AgentKind.Ephemeral });});Session limits enforced by the Coordinator:
| Limit | Value |
|---|---|
| Max depth | 10 |
| Max children per session | 10 |
| Max active sessions per zone per application | 50 |
| Max active sessions per application across zones | 200 |
Delegate authority to a target session
Section titled “Delegate authority to a target session”delegate() creates a directed authority edge from the current session to the target session. The target can then exchange that edge for a mandate scoped to the delegated permissions.
TypeScript:
await caracal.spawn(async () => { const sourceCtx = caracal.current()!;
// targetAgentSessionId comes from the spawned child or another agent await caracal.delegate( { to: targetAgentSessionId, toApplicationId: 'payments-worker', scopes: ['payment:submit', 'payment:read'], ttlSeconds: 300, }, async () => { const ctx = caracal.current()!; console.log('delegation edge:', ctx.delegationEdgeId); // The delegated context is now active } );});Python:
from caracalai_sdk import DelegationConstraints
async with caracal.spawn() as source: async with caracal.delegate( to=target_agent_session_id, to_application_id='payments-worker', scopes=['payment:submit', 'payment:read'], constraints=DelegationConstraints( resources=['resource://payments'], max_depth=2, ), ttl_seconds=300, ) as delegated: print('edge:', delegated.delegation_edge_id)Go:
err = client.Spawn(ctx, func(ctx context.Context) error { return client.Delegate(ctx, sdk.DelegateOptions{ To: targetAgentSessionID, ToApplicationID: "payments-worker", Scopes: []string{"payment:submit", "payment:read"}, Constraints: &sdk.DelegationConstraints{ Resources: []string{"resource://payments"}, MaxDepth: 2, }, TTLSeconds: 300, }, func(ctx context.Context) error { cc, _ := sdk.Current(ctx) fmt.Printf("edge: %s\n", cc.DelegationEdgeID) return nil })})Attach caveats to a delegation edge
Section titled “Attach caveats to a delegation edge”DelegationConstraints / constraints_json narrows what the target can do:
| Constraint | Effect |
|---|---|
resources | Restricts the edge to specific resource identifiers |
actions | Restricts the allowed action strings |
max_depth | Caps how many more hops authority can flow through from the target |
expires_at | Hard expiry for the edge independent of the session TTL |
The STS validates all constraints on each exchange. A constraint violation fails the exchange before OPA is evaluated.
Read the delegation graph from the CLI
Section titled “Read the delegation graph from the CLI”Inspect active sessions and their delegation edges:
# List all active agent sessions in the zonecaracal agent list --json
# Show the child sessions of a specific sessioncaracal agent tree sess-abc123
# Show inbound edges (authority flowing IN to the session)caracal delegation inbound sess-abc123
# Show outbound edges (authority flowing OUT from the session)caracal delegation outbound sess-abc123
# Walk the full delegation chain from a specific edgecaracal delegation traverse edge-def456Revoke a delegation edge
Section titled “Revoke a delegation edge”Revoking an edge cascades forward through the entire subgraph. All sessions that received authority through the revoked edge are terminated:
caracal delegation revoke edge-def456The Coordinator runs the cascade revocation CTE and publishes a caracal.sessions.revoke event for every affected session. The Gateway blocks requests from affected sessions within seconds.
Terminate a specific agent session and all its descendants:
caracal agent terminate sess-abc123Suspend and resume
Section titled “Suspend and resume”Suspension affects the entire subtree. Active mandates for suspended sessions are still valid until their 15-minute TTL expires, but new exchanges are blocked:
caracal agent suspend sess-abc123 # Block new exchanges for entire subtreecaracal agent resume sess-abc123 # Re-enable the subtreeVerify a mandate includes the expected chain
Section titled “Verify a mandate includes the expected chain”On the receiving side, check that the delegation chain contains the expected orchestrator application:
TypeScript:
import { verify } from '@caracalai/identity';
const claims = await verify(token, { issuer: process.env.CARACAL_STS_URL!, audience: 'resource://payments', requireChainContains: ['orchestrator-app'],});Go:
claims, err := identity.Verify(token, identity.Config{ Issuer: "http://sts:8080", Audience: "resource://payments", RequireChainContains: []string{"orchestrator-app"},})What to read next
Section titled “What to read next”- Author a Rego Policy —
input.delegation_edgeandinput.context.delegation_edge_idare available in policy - Tail and Query the Audit Stream —
determining_policiesanddiagnosticsper delegation exchange - Step-Up Re-Authentication — combine delegation with step-up for high-value actions