---
title: Typed Delegation Constraints
description: Resource, TTL, hop, budget, and approval restrictions that travel with a delegation edge.
pageType: concept
keywords: [constraints, resources, TTL, max_hops, budget, delegation, restriction]
---

Delegation constraints are typed restrictions attached to a delegation edge. They travel with the edge and are enforced by the Coordinator at creation time and by the STS at every token exchange that relies on the edge.

## What constraints are

When application A creates a delegation edge to application B, A can attach constraints that restrict what B can do with the delegated authority. These constraints are stored in `constraints_json` on the delegation edge and cannot be modified after the edge is created — they are part of the edge's definition.

Constraints narrow authority; they cannot expand it. B can request only what the edge's `scopes` permit, and constraints impose additional limits on top of that.

Unknown constraint keys are rejected. The current typed constraint fields are `resources`, `ttl_seconds`, `max_hops`, `max_depth`, `budget`, `policy_approved`, `expires_at`, and `broad_reason`.

## Constraint types

### `resources`

Limits delegated authority to explicit resource identifiers. This is the preferred shape. A delegation without a resource binding is a broad delegation and requires elevated Coordinator permission plus audit-visible justification.

### `ttl_seconds`

Caps the lifetime of any resource mandate issued using this delegation edge.

When the STS issues a mandate to a delegated agent, it caps the mandate TTL at the lesser of:
- The requested `ttl_seconds` in the exchange request
- The edge's `ttl_seconds` constraint
- The system-wide resource TTL ceiling (15 minutes)

If B requests a token with a 30-minute TTL but the edge has `ttl_seconds = 300`, the token expires in 5 minutes.

The edge itself also carries an `expires_at` timestamp. The STS rejects any exchange for an expired edge.

### `max_hops`

Limits how many times authority can be re-delegated along the chain.

If A→B has `max_hops = 1`, B cannot delegate its authority further to C. The STS checks the current hop count against `max_hops` before allowing an exchange on the edge. A delegation chain that exceeds this limit is rejected.

The system-wide maximum depth is 10 (`MAX_DEPTH`). Edge-level `max_hops` can be more restrictive but never more permissive than the system limit.

:::note
The `max_depth` field in the SDK's `DelegationConstraints` type is normalized to `max_hops` in the Coordinator. Both names refer to the same constraint.
:::

### `budget`

An integer cap on how many scopes B can request in a single token exchange. This is an additional restriction on top of the edge's `scopes` array.

The `budget` constraint is useful when A wants to delegate a broad set of scopes but prevent broad resource mandates. For example: the edge `scopes` are `["read", "write", "transfer"]` but the budget is `1`, so B can request only one of those scopes per exchange.

The STS first checks that the exchange's requested scopes are contained within the edge scopes, then checks that the number of requested scopes does not exceed the budget before evaluating the policy.

### `policy_approved`

A boolean flag that a policy can check to distinguish pre-approved delegations from ad hoc ones.

This constraint has no standalone authorization effect in the STS — it is a signal for policy rules. A policy can inspect `input.delegation_edge.constraints_json.policy_approved`, but policy must still allow the final resource and scope request.

## How constraints are enforced

At token exchange, after the delegation edge is loaded and validated as active, the STS applies constraints in sequence:

```
1. Is the edge active and not expired?        → reject if no
2. Does the target session match?             → reject if no
3. Are requested scopes ⊆ edge scopes?        → reject if no
4. Are requested resources allowed?           → reject if no
5. Is requested scope count ≤ budget?         → reject if budget set and no
6. Is the token TTL ≤ edge ttl_seconds?       → cap to edge limit
7. Is the hop count ≤ max_hops?               → reject if exceeded
8. Pass edge metadata to OPA for evaluation   → allow / deny
```

Constraint enforcement happens before OPA evaluation. If any constraint check fails, the STS returns an error without calling OPA. The failure is recorded in the audit ledger.

The edge's typed `constraints_json` is also passed to OPA in `input.delegation_edge.constraints_json`, so policies can write additional logic against constraint values — for example, denying exchanges for edges with `max_hops > 2`, or requiring `policy_approved = true` for high-value resources.

## Constraint immutability

Constraints are set at edge creation and cannot be changed. An edge with incorrect constraints must be revoked and replaced. This immutability is intentional: it ensures that the authority given at delegation time does not silently expand.

## Example delegation with constraints

```typescript
// TypeScript SDK
await caracal.delegate(
  {
    to: childAgentSessionId,
    toApplicationId: "app-b",
    scopes: ["resource://payments:read"],
    constraints: {
      resources: ["resource://payments"],
      ttlSeconds: 300,
      maxDepth: 1,
    },
  },
  async () => {
    // Child agent has authority for 5 minutes, read-only, no sub-delegation
  }
);
```

## Next steps

- [Delegation Graph](./delegation/) — how edges form a tracked graph with cycle detection and cascade revocation
- [Sessions and Revocation](./sessions-revocation/) — what happens to sessions when an edge is revoked
- [Authority Model](./authority-model/) — how the STS enforces constraints before calling OPA
