Delegation
Delegation lets one subject (the delegator) grant another subject (the delegate) the ability to act on its behalf for a defined set of actions. The most common use case is an AI agent acting on behalf of a human user — but delegation chains extend naturally to multi-agent systems, where an orchestrator agent delegates a sub-task to a specialized sub-agent.
How it works
When a delegation exists and the delegate calls /v1/evaluate, Vengtoo applies the intersection model:
Delegate's effective access = delegate's own policies ∩ delegator's policies
The delegate can never exceed the delegator's own permissions. If the delegator loses access to a resource, or has their own access revoked, the delegate immediately loses it too — no propagation delay, no stale grants.
Human user (delegator) ──grants──▶ AI agent (delegate)
│ │
own policies own policies
└──────────┐ ┌────────────────-─┘
▼ ▼
intersection = effective access
Delegation chains (multi-hop)
A delegate can itself become a delegator — for example, an orchestrator agent that was delegated access by a human can delegate a narrower slice of that access to a sub-agent. When this happens, Vengtoo walks the full chain and requires every principal in it to independently allow the action:
Human (alice) ──▶ Orchestrator agent ──▶ Sub-agent
│
requests access
▼
finalDecision = sub-agent allows AND orchestrator allows AND alice allows
If any principal in the chain — at any depth — would deny the action, the entire chain is denied. Authority only narrows as it moves down a chain; it can never widen. This is what prevents a sub-agent with its own broad permissions from acting beyond what the original human actually authorized, no matter how many agents are involved.
Chain depth is capped at 3 hops. A delegation that would push any chain beyond that limit is rejected at creation time.
Scope
You can restrict what a delegation covers using the scope field — a list of action identifiers the delegate is allowed to perform on the delegator's behalf.
{
"delegator_id": "USER_ID",
"delegate_id": "AGENT_ID",
"scope": ["document:read", "document:list"],
"expires_at": "2026-12-31T23:59:59Z"
}
scope is matched as an exact allowlist against the action name in the request. An action not in the scope list is denied immediately — before any policy evaluation runs — regardless of what either party's own policies would otherwise allow.
When scope is omitted
- For a root delegation (the delegator is not itself a delegate of anyone), omitting
scopemeans the delegation is unrestricted — the intersection of both parties' own policies applies with no additional scope ceiling. - For a sub-delegation in a chain (the delegator is itself a delegate), omitting
scopemeans the sub-delegation inherits the delegator's own scope. A sub-agent can never end up less restricted than the scope the principal above it was granted, even if no explicit scope is written on the new delegation.
If you do specify a scope on a sub-delegation, it must be a subset of the delegator's own scope. Attempting to widen scope beyond the parent's is rejected at creation time.
Expiry
Set expires_at to automatically revoke the delegation at a specific time. Omit it for an indefinite delegation. Delegations can also be revoked explicitly at any time via DELETE /v1/delegations/{id} — effective immediately, and immediately reflected for every hop downstream in a chain.
This is a different timer from the HITL decision reuse window — a delegation's expires_at governs how long the relationship exists; an HITL approval's approval_ttl_hours governs how long a single human decision is reused. The two can both apply to the same request independently.
Constraints
- Delegator and delegate must be different subjects.
- Both subjects must already exist in your tenant before a delegation can be created.
- Chain depth is capped at 3 hops — enforced at creation time.
In the evaluation response
When access is granted through a delegation, the response reflects the delegate's own policy resolution:
{
"decision": true,
"context": {
"reason_code": "ALLOWED",
"access_path": "direct",
"policy_id": "pol-456"
}
}
When access is denied because the requested action is outside a delegation's scope, a distinct reason code is returned — before any policy evaluation runs:
{
"decision": false,
"context": {
"reason_code": "delegation_scope_denied",
"reason": "action not in delegation scope"
}
}
When the delegate's own policies would allow the action but a principal higher in the delegation chain would not, another distinct reason code is returned:
{
"decision": false,
"context": {
"reason_code": "DELEGATOR_DENIED",
"reason": "delegator <id> does not have access to perform this action"
}
}
DELEGATOR_DENIED distinguishes "the agent was blocked by a principal above it in the chain" from "the agent's own policy doesn't allow this" — useful for debugging and policy auditing.
Audit trail for chains
Every delegated evaluation records a delegation_chain field in the audit log — an ordered list of the delegation IDs involved, from the immediate delegator up to the root. This means the audit log for a 3-hop chain traces every principal involved, not just the subject that made the request, so you can reconstruct exactly who authorized what at each level.
When to use delegation
| Scenario | Use delegation? |
|---|---|
| AI agent acting on behalf of a specific user | Yes |
| Orchestrator agent delegating a sub-task to a specialized sub-agent | Yes — see Delegation chains |
| Service-to-service calls with a fixed identity | No — give the service its own policies |
| Temporary admin access for a contractor | No — use JIT Access instead |
| User sharing their access with a teammate | Yes, with a scoped expires_at |
Related
- Access Paths — how delegation fits into the evaluation flow.
- JIT Access — time-boxed access without the intersection constraint.
- HITL — pausing a sensitive action for human approval, which can apply independently of any delegation chain.
- Delegation API — create, list, get, and revoke delegations.