Skip to Content
ConceptsConditions on rules (CEL)

Conditions on rules

A policy rule says what is allowed, denied or held: a platform, a tag, or a platform:scope. A condition says when that rule applies. It is a single CEL  expression attached to a rule; the rule matches only if the expression is true for the call being made.

Conditions do not add a fourth kind of rule and they cannot grant anything on their own. A condition can narrow an allow (“allow, but only in business hours”), widen a deny (“deny when the amount is over the ceiling”) or widen an ask (“ask, unless a human approved this in the last ten minutes”). Deny-wins precedence and deny-by-default are untouched.

Available on the Team plan and above. Edit them in Governance → Policies → (a policy) → Conditions (advanced) under the capability grid.

Three examples

Business hours in Brisbane, Monday to Friday, on an allow rule:

time.now.getHours("Australia/Brisbane") >= 9 && time.now.getHours("Australia/Brisbane") < 18 && time.now.getDayOfWeek("Australia/Brisbane") in [1, 2, 3, 4, 5]

Skip the human ask when the same agent was approved for the same scope in the last ten minutes, on an ask rule. On an ask rule the condition answers “should this rule ask?”, so it is the negation:

!(has(approval.last_approved_at) && time.now - approval.last_approved_at < duration("10m"))

An amount ceiling on the request body, on an allow rule for stripe:charges.write:

request.method == "POST" && has(request.body.amount) && request.body.amount <= 50000 && request.body.currency == "aud"

What a condition can read

RootFields
agentid, status, parent_id (child agents), owner_id, expires_at
orgid
platformid
connectionid
scopeid, tags (the scope’s built-in and custom tags)
requestmethod, path (never the query string), body (top-level string/number/boolean fields only, at most 64, strings up to 1 KB; nested objects and arrays are not exposed)
timenow (a timestamp; use getHours("Australia/Brisbane")-style accessors with an explicit time zone)
approvallast_approved_at, present only if an owner has approved this agent for this scope on this platform before; guard with has()
sessionid (caller-asserted, attribution only)
callerrole, owner_id, acting_as_user_id

Anything else is an unknown variable and is rejected when you save.

Fail-closed, always

Rule kindcondition truecondition falseerror or engine unavailable
allowappliesdoes not applydoes not apply
denyappliesdoes not applyapplies
askappliesdoes not applyapplies

“Error” covers a syntax error, a type error, an unknown field, an expression over the size limits, or one that does not evaluate to a boolean. “Unavailable” is what an organisation below the Team plan gets: an existing condition keeps enforcing in the safe direction after a downgrade (allows stop applying, denies and asks keep applying) and shows in the audit trace as outcome: "unavailable".

Where the answer is visible

  • The audit row’s policy_trace carries a rule_predicate_evaluated event with the rule id and the outcome, and the signed receipt carries the matched events, so an auditor can see which condition decided a call.
  • GET /v1/agent/permissions advertises scopes whose verdict depends on a condition under conditionalScopes, and the permissions version changes when you edit one, so a cached agent revalidates.
  • The policy editor’s preview treats a conditional allow as “allowed, conditional”, a conditional deny as not matching, and a conditional ask as asking, exactly as discovery does.

Honest limits

  • Conditions are evaluated when the call is made, not again when a held call is approved. A business-hours allow that queued at 17:59 and was approved at 18:05 executes.
  • The AuthZEN and TRQP oracles have no request context, so a condition that reads request.* cannot be satisfied there and the oracle answers as if it were false; guard with has(request.method) if the rule must also answer from an oracle.
  • Guardrails (org-wide rules) do not take conditions in this version.
  • The open-core MCP broker does not evaluate conditions; they are a hosted-plane feature.
  • There is no org time zone setting yet: name the zone in the expression.
Last updated on