Skip to content

delegation.rego

Hard caps on what delegated sub-agents can do. The principle: a child agent is more constrained than its parent, both in delegation chain depth and in transactional authority.

Like security.rego, every denial here becomes a SECURITY_CRITICAL decision in main.rego — no escalation path, no scope override.

Package: kitelogik.delegation · Source: kitelogik/policies/delegation.rego

Rules

TriggerMatch
Delegation chain too deepcontext.delegation_depth > 2
Depth-1 refund > $50action == "approve_refund" and delegation_depth == 1 and args.amount > 50
Depth-1 refund with malformed amountaction == "approve_refund" and delegation_depth == 1 and args.amount < 0 (catches null, false, true, negatives)
Depth-2+ refund (any amount)action == "approve_refund" and delegation_depth >= 2

The < 0 rule is a deliberate guard against OPA's structural ordering — in OPA, null < bool < number, so null > 50 evaluates false (not an error) and would silently bypass the cap. The < 0 check catches all three malformed-amount cases.

Source

rego
package kitelogik.delegation

import future.keywords.if
import future.keywords.in

default allow := false
default deny := false

# Hard cap: no delegation chain deeper than 2
deny if { input.context.delegation_depth > 2 }

# Depth-1 delegates: refund cap is $50
deny if {
    input.action == "approve_refund"
    input.context.delegation_depth == 1
    input.args.amount > 50
}

# Depth-1 delegates: malformed amounts (null/bool/negative) are blocked
deny if {
    input.action == "approve_refund"
    input.context.delegation_depth == 1
    input.args.amount < 0
}

# Depth-2+ delegates: no refunds at all
deny if {
    input.action == "approve_refund"
    input.context.delegation_depth >= 2
}

How main.rego uses this module

Same pattern as security — checked first, overrides allows, gets SECURITY_CRITICAL risk tier, no HITL escalation:

rego
deny if { delegation.deny }

risk_tier := "SECURITY_CRITICAL" if { delegation.deny }

requires_hitl if {
    not allow
    not security.deny
    not delegation.deny    # ← excluded from HITL
}

Where this fits with agent_lifecycle.rego

agent_lifecycle.rego governs the spawn and delegate events themselves (can this agent create a child?). delegation.rego governs what the resulting child agent is allowed to do (the constraints on its action surface).

So a complete delegation flow runs through both:

  1. Parent agent fires agent.delegate event → agent_lifecycle.rego gates
  2. Child session is created with delegation_depth = parent.depth + 1
  3. Child fires a tool_call event → delegation.rego gates the tool's authority based on the new depth

Extending in your own project

Same package, new file:

rego
package kitelogik.delegation

import future.keywords.if

# Restrict child agents from sending notifications
deny if {
    input.action == "send_notification"
    input.context.delegation_depth > 0
}

TIP

The hard depth cap of > 2 is the OSS default. To raise (or lower) it, add a deny rule in your own package with the desired threshold — since denies merge across files, the stricter rule wins.

Released under the Apache 2.0 License.