Skip to content

Implement Multi-Agent Delegation

Use Delegation when one Session needs to hand a narrower slice of authority to another Session. The SDK creates the Delegation and carries the context; the web console shows the graph and impact.

  • Two live Sessions and a parent that already holds the resource authority being delegated.
  • A specific resource, scope subset, positive TTL, and intended maximum hop count.
  • An authenticated work channel for transferring the opaque Delegation ID to the target Session.

session() always starts the child under the same application as the parent. Narrow the child’s authority only when it should hold less than the parent. To hand authority across applications, use delegate() with a peer Session under the other application; it returns the Delegation, and the receiver presents it with acceptDelegation().

flowchart LR
  Parent["Parent Session"] --> Start["Start child with narrowed authority"]
  Start --> Delegation["Bounded Delegation"]
  Delegation --> Child["Run child with delegated context"]
  Child --> Exchange["Exchange for resource mandate"]
  Exchange --> Audit["Inspect audit and graph"]

Python uses Authority.narrow(...), await caracal.delegate(...), and async with caracal.accept_delegation(edge_id, validate=True). Go uses AuthorityNarrow(...), Delegate, and AcceptDelegation. The language SDK references define exact option types; the workflow and wire authority are equivalent.

Starting a child with less authority than its parent needs no explicit hand-off - pass Authority.narrow(...) when starting the child Session, exactly as shown in your SDK guide. This page’s subject is the explicit flow between two Sessions that already exist.

The issuer holds the authority and knows the receiver’s Session ID; the receiver presents the returned Delegation ID. Two sides, one edge:

// Issuer side: Anton delegates read-only PiperNet access
// to a peer Session owned by the Fiona application.
const delegation = await caracal.delegate({
toSessionId: receiverSessionId, // shared by the receiver up front
toApplicationId: fionaApplicationId, // omit for same-application peers
resourceId: 'resource://pipernet',
scopes: ['pipernet:read'],
constraints: { maxHops: 1 },
ttlSeconds: 600,
})
await sendOverWorkChannel({ delegationId: delegation.delegationId })
// Receiver side (Fiona): present the offered Delegation inside the
// target Session's context. validate: true confirms it is live first.
await caracal.session(async () => {
const { delegationId } = await readFromWorkChannel()
await caracal.acceptDelegation(
delegationId,
async () => {
const governed = caracal.transport({ scopes: ['pipernet:read'] })
const target = caracal.gatewayRequest('resource://pipernet', '/reports')
await governed(target.url)
},
{ validate: true },
)
})

Every mint inside the accepted block presents the Delegation, so the STS enforces its scopes, resource bound, TTL, and hop budget - and revoking the edge cuts the receiver off mid-run.

  1. Open the web console for your deployment.
  2. Select Delegation.
  3. Inspect active edges, inbound edges, outbound edges, and traversal.
  4. Use impact before revoking an edge.
  5. Check Audit for the delegated exchange and resource decision.

The issuer and receiver share the opaque Delegation ID over their authenticated work channel, such as a queue message, RPC field, or task payload. Creation is only an offer and does not mutate receiver context. Possessing and presenting this target-Session-bound ID through acceptDelegation() is receiver consent; STS also checks the receiving application and live target Session. Use acceptDelegation(delegationId, fn, { validate: true }) to confirm that exact ID with Coordinator before work runs under it. Every presentation emits a delegation.accept event.

ConstraintGood default
ScopesSmall subset of parent authority.
TTLRequired; use minutes, not days.
Hop count1 unless a deeper graph is intentional.
ResourceSingle resource whenever possible.
SymptomCheck
Delegate requires an active SessionCall delegation inside session() or a bound Caracal context.
Resource denies delegated requestConfirm the edge, ancestry, policy grant, and required scopes remain live.
Chain validation failsConfirm authoritative parent links are continuous; configure requireChainContains separately at verifiers when needed.
Revocation did not affect childConfirm cascade revocation and resource-server revocation consumers.

Mint the allowed subset, attempt one broader scope, revoke the edge, and retry. The subset must succeed; broadening must fail server-side; after revocation neither the child nor descendants may mint or pass resource verification.

Inspect the graph and request trace in Tail and Query the Audit Stream.