Start free

Policy & decisions

Policy turns a captured action into a decision. HESO evaluates an ordered, first-match-wins rule set and returns one of four decision paths in under 2 ms.

Every action your agent takes is captured as an ActionDetail— a verb, a tool name, a target host, and a set of fields. Before that action is signed into a receipt, HESO runs it through your policy. The policy reads the action, picks exactly one rule, and that rule’s decision decides what happens to the call. The chosen rule and its verdict are then recorded on the receipt, so anyone verifying it later can see which sentence routed the action.

The decision

A decision is the single output of policy evaluation. The Rust core computes it — the same core that signs and verifies — so a verdict is byte-identical whether it runs on your server, in Node, or in the browser. It is a lookup against an ordered rule set, not a model call, which is why it completes in under 2 ms.

The decision answers one question: what should happen to this action right now? The four possible answers are the decision paths below. Whichever path the winning rule names becomes the decision_path on the receipt’s PolicyOutcome.

The four decision paths

Every rule resolves to one of four paths. allow lets the action through. block stops it. redact rewrites sensitive fields before signing. require_approval routes the action to a human.

PathPlain languageWhat happens
allowallowThe action proceeds. It is still captured, signed, and chained — allow means “permitted,” not “unrecorded.”
blockblockThe action is refused and never executed. The attempt is still recorded.
redactredactSensitive fields are replaced with a hash before the action is signed, so the raw values never reach a receipt or our servers. See Redaction.
require_approvalroute-to-humanThe action pauses and is routed to a human approver. When they co-sign it with their own key, the receipt becomes L1. See Human approval.
redact and require_approval can combine

A rule names exactly one decision path, but the redaction path and an approval are not mutually exclusive across a workflow: fields redacted under a redact rule stay redacted in the post-redaction fields that a later approver sees. The values an approver reviews are always the redacted ones.

First match wins

A policy is an ordered list of rules. HESO walks them in order and stops at the first rule whose subject, verb, scope, and conditions allmatch the action. That rule’s decision is the verdict. No later rule is checked, and rules never merge or vote — the first complete match decides, full stop.

Because order is meaningful, write your rules from most specific to most general:

  • Put tight, high-stakes rules (a single workflow, a narrow host, a hard cap) near the top.
  • Put broad catch-alls (subject “any,” scope “*”) near the bottom.
  • A rule with enabled = false is skipped entirely, as if it were not in the file.

Here is one rule from a heso.toml. It matches a payment over $5,000 in the vendor-payouts workflow and routes it to a human:

heso.toml
[[rule]]id = "pay-cap"order = 10enabled = truesubject = { kind = "workflow", value = "vendor-payouts" }verb = "payment"scope = "*"conditions = [  { field = "amount_usd", op = "gt", value = 5000,    display = "amount over $5,000" },]decision = "require_approval"approvers = ["finance-lead"]sla_minutes = 60

The full file format — the [[rule]] block, every field, and how order drives evaluation — is covered in Policy files.

What a rule matches on

A rule matches an action only when all four of its facets agree with the captured action.

  • subject — who is acting. kind is any, workflow, or account, with an optional value to pin a specific workflow or account.
  • verb — what kind of action. Either "any" or one of the seven verbs (llm_call, tool_call, http_request, payment, data_export, account_change, delete).
  • scope — where it points. A host glob matched against the action’s target_host, or "*" for any host.
  • conditions— the fine print. An array of typed checks on the action’s fields. Every condition must pass for the rule to match.

Conditions use typed operators — gt, lt, eq, in, matches, and the rest — with values typed as numbers, string arrays, or strings depending on the operator. The full operator set and how values are typed is in Conditions & operators.

The policy outcome

Once a rule wins, HESO records it on the receipt as a PolicyOutcome. This is the lasting proof of which rule gated the action and why — it travels inside the signed ActionContent, so a verifier sees the exact verdict without needing your policy file.

FieldMeaning
rule_idThe id of the rule that matched.
rule_displayThe human-readable sentence that routed the action — the “why” in plain English.
matched_conditionsThe conditions that passed, each as { field, op, value }.
decision_pathOne of the four paths above — the verdict itself.

The rule_displayis the sentence a reviewer reads. It is the policy author’s own words, carried through to the receipt, so an auditor never has to work a rule id back into what it meant. Here is the PolicyOutcome the rule above would write:

policy-outcome.json
{  "rule_id": "pay-cap",  "rule_display": "Require approval to pay over $5,000",  "matched_conditions": [    { "field": "amount_usd", "op": "gt", "value": "5000" }  ],  "decision_path": "require_approval"}

Deny by default

Policy fails closed. If an action lands in a dangerous lane — a payment, a delete, an account change, or a large data export — and no rule matches it, the default is require_approval, not allow. An unknown dangerous action is routed to a human rather than waved through.

On top of that, dangerous lanes carry a built-in pinned floor that the engine enforces when it loads the policy. A policy may tighten a floor — adding conditions, lowering a cap, narrowing a scope — but it can never allow-without-approval a dangerous lane. A policy that tries is rejected at load time with a [FLOOR_BYPASS] error naming the offending rule id and verb; a broken policy is rejected with [PARSE]. These checks run in the browser too, so a bad policy is caught before it can ever be deployed.

What a decision proves — and what it doesn't

The PolicyOutcome on a receipt proves the operator authorized this action under a known rule, and at L1 that a person approved it with a device-held key. It records which rule fired and who stood behind it — what was authorized, not whether the action succeeded downstream.

Next steps