Start free

Conditions & operators

Conditions narrow a rule down to the actions it governs. Each one is a field, an operator, and a typed value the engine checks against the captured action.

A policy is an ordered list of rules. The first rule whose subject, verb, scope, and conditions all match decides the action. This page covers that last part — the conditions. A condition reads one field off the captured action, applies an operator, and compares it to a value you wrote. Get the operator and the value type right and the rule fires exactly when you mean it to. Get them wrong and the rule quietly never matches.

Anatomy of a condition

A single condition has four parts: { field, op, value, display }. The field names what to read off the action, op is the operator, value is the typed value to compare against, and display is the plain sentence that ends up in the receipt’s matched_conditions so a reviewer can read why the rule fired.

heso.toml
[[rule]]id = "pay-cap"order = 10enabled = trueverb = "payment"scope = "*"decision = "require_approval" [[rule.conditions]]field = "amount_usd"op = "gt"value = 5000display = "amount is over $5,000"

A rule can carry several conditions. All of them must hold for the rule to match — conditions are combined with AND, never OR. If you need OR, write two rules. Because the policy is first-match-wins, order the more specific rule first.

Tip

Always write a display sentence. It is the plain line that shows up in the signed receipt and in simulation output, so an auditor never has to read TOML to understand the verdict.

The operators

The engine evaluates ten operators. The numeric comparisons read the field and the value as numbers; the rest compare strings, membership, presence, or a glob pattern.

OperatorGlyphMeaning
gt>Field is greater than the value.
lt<Field is less than the value.
gte>=Field is greater than or equal to the value.
lte<=Field is less than or equal to the value.
eq==Field equals the value.
neq!=Field does not equal the value.
ininField is one of the values in the array.
not_innot_inField is not one of the values in the array.
existsexistsThe field is present on the action.
matchesmatchesField matches a glob or pattern.
Note

Every operator is evaluated by the one Rust core, so a verdict is byte-identical whether the policy runs in the SDK on your server or in the browser. The same check that decides an action in production is the one your simulatorruns, with policy evaluation under 2 ms.

Typed values

The operator decides what shape value must take. The engine parses your TOML value into typed JSON, so writing a number where a string belongs — or the reverse — is the most common reason a rule fails to fire.

Numeric operators want numbers

gt, lt, gte, and lte compare numerically. Write the value as a bare number, not a quoted string.

heso.toml
[[rule.conditions]]field = "amount_usd"op = "gt"value = 5000display = "amount is over $5,000"

Membership operators want string arrays

in and not_in test membership, so the value is an array of strings. The condition holds when the field is — or, for not_in, is not — one of the listed entries.

heso.toml
[[rule.conditions]]field = "region"op = "not_in"value = ["us", "eu"]display = "region is outside the US or EU"

exists ignores its value

exists only asks whether the field is present on the action. Its value is ignored, but the schema still expects one — write true by convention.

heso.toml
[[rule.conditions]]field = "api_key"op = "exists"value = truedisplay = "an api_key field is present"

eq and neq keep the parsed type

For eq and neq the engine keeps the value as a boolean or a number when it parses as one, and otherwise treats it as a string. So value = true is a boolean, value = 3 is a number, and value = "gpt-4o" is a string.

heso.toml
[[rule.conditions]]field = "model"op = "eq"value = "gpt-4o"display = "the model is gpt-4o"

Fields you can match

Most conditions read the action’s fields map — the keyword arguments captured from the call, like amount_usd, region, or model. You can also match on the shape of the action itself: the verb and the target_host are matched through a rule’s verb and scope, and conditions narrow things further from there.

  • verb — the action verb (llm_call, tool_call, payment, and the rest), set on the rule itself rather than as a condition.
  • scope — a host glob (or *) matched against the action’s target_host; omitted for llm_call and internal tools.
  • fields — any captured keyword argument, addressed by its name, evaluated with the operators above.
heso.toml
[[rule]]id = "redact-export-keys"order = 20enabled = trueverb = "data_export"scope = "*.internal"decision = "redact" [[rule.conditions]]field = "rows"op = "gt"value = 10000display = "export is over 10,000 rows"
Conditions see post-redaction fields

The fields map a condition reads is the post-redaction map — the same values that get signed into the receipt. If a field is redacted before signing, its plaintext is no longer there to compare against. Gate on the values you need before you redact them, and keep a non-sensitive signal (like region or amount_usd) for the conditions that must still run. See Redaction for the mechanics.

Worked examples

Four common shapes, one per decision path.

Route large payments to a human

A numeric gt on amount_usd sends any payment over $5,000 to an approver instead of letting it through.

heso.toml
[[rule]]id = "pay-cap"order = 10enabled = trueverb = "payment"scope = "*"decision = "require_approval" [[rule.conditions]]field = "amount_usd"op = "gt"value = 5000display = "amount is over $5,000"

Block exports outside allowed regions

A not_in over a string array blocks a data export whenever its region is neither us nor eu.

heso.toml
[[rule]]id = "block-offshore"order = 20enabled = trueverb = "data_export"scope = "*"decision = "block" [[rule.conditions]]field = "region"op = "not_in"value = ["us", "eu"]display = "region is outside the US or EU"

Allow only a model family

A matches glob lets through any GPT-4 variant — gpt-4o, gpt-4-turbo, and so on — with a single pattern.

heso.toml
[[rule]]id = "gpt4-only"order = 30enabled = trueverb = "llm_call"scope = "*"decision = "allow" [[rule.conditions]]field = "model"op = "matches"value = "gpt-4*"display = "the model is a GPT-4 variant"

Redact when a secret is present

An exists condition fires the redact path whenever a call carries an api_key field, so the secret never reaches the receipt as plaintext.

heso.toml
[[rule]]id = "redact-keys"order = 40enabled = trueverb = "tool_call"scope = "*"decision = "redact" [[rule.conditions]]field = "api_key"op = "exists"value = truedisplay = "an api_key field is present"

Combine conditions with AND

Conditions on one rule are joined with AND. This rule only routes to a human when the payment is gte $1,000 and the environment is production — both must hold.

heso.toml
[[rule]]id = "approve-large-prod-payouts"order = 5enabled = trueverb = "payment"scope = "api.stripe.com"decision = "require_approval" [[rule.conditions]]field = "amount_usd"op = "gte"value = 1000display = "amount is $1,000 or more" [[rule.conditions]]field = "env"op = "eq"value = "prod"display = "running in production"
Careful

Conditions narrow a rule, they do not loosen a floor. A condition can never let a dangerous lane through without approval — the engine rejects such a policy at load with a [FLOOR_BYPASS] error naming the offending rule. Read Pinned floors before you write a rule that allows a payment, delete, account change, or large export.

Next steps