For the complete documentation index, see llms.txt. This page is also available as Markdown.

Compound Policies

What you'll learn: How to combine rules using all_of, any_of, and not, including the critical not(all_of(...)) pattern for blocking triggers.


Compound rules

Three compound rules let you combine conditions:

Rule
Passes when
Use for

all_of

All sub-conditions pass

Multiple requirements that must all be met

any_of

Any sub-condition passes

Alternative conditions, any one sufficient

not

The sub-condition fails

Inverting a condition

Compound rules recurse freely: all_of can contain any_of, which can contain not, which can contain a primitive rule.

The critical gotcha: not(all_of(...))

Rule functions return True to pass and False to block. This is the most common source of compound policy authoring mistakes.

If your intent is "block when conditions A, B, and C are all present," the naive approach is:

{"rule_type": "all_of", "params": {"conditions": [A, B, C]}}

This is wrong. all_of returns True (passes) when all conditions are met. It would block every step where any condition is not met — the opposite of what you want.

The correct pattern:

{
  "rule_type": "not",
  "params": {
    "condition": {
      "rule_type": "all_of",
      "params": {"conditions": [A, B, C]}
    }
  }
}

How it works:

  • all_of returns True when all danger conditions are present (= dangerous situation detected).

  • not inverts to False (= violated = blocked).

  • If any condition is absent, all_of returns False, not inverts to True (= passes).

Worked example: the taint policy

The OWASP default template's taint policy uses this pattern. It blocks high-impact actions when external content has entered the task and no fresh gate precedes the action.

Reading the logic:

  1. The outer not means: "block if the inner all_of passes."

  2. The inner all_of checks three conditions:

    • Current step is a high-impact action (exec, outbound message, or mutating resource call).

    • External content has been fetched earlier in the task.

    • The current step is NOT directly preceded by a gate.

  3. If all three are true → dangerous → all_of passes → not blocks.

  4. If any is false → safe → all_of fails → not passes.

Another example: PII + product data + model requires approval

"If the agent has read customer-data AND product-data AND called a model, then sending an outbound message requires a human-approval gate."

Note: this example uses bare all_of (not wrapped in not), because the not inside the conditions already inverts the gate check. The all_of passes when all conditions are met AND no gate exists — which means it returns True when the situation is safe (gate present) and False when dangerous (gate missing). Wait — actually, let's re-read. The inner not(step_requires_gate) returns True when no gate exists. So all_of returns True when all conditions hold AND no gate → this is a blocking trigger being detected as "passing." This actually needs to be wrapped in not(all_of(...)) as well. Be careful with your logic.

Debugging compound policies

Use the engine's explain() method to see per-node pass/fail:

Incidents from compound policies carry the full condition tree in violation_details.


Next steps

Last updated