Start free

Actions & verbs

An action is the unit HESO governs: one thing your agent tries to do. Each captured action is described by an ActionDetail and labelled with one of seven verbs.

What is an action

An action is a single thing your agent attempts: one model completion, one tool call, one outbound HTTP request, one payment. HESO captures the action before it runs. That way policy can decide on it — allow it, block it, redact a field, or route it to a human — and the decision can be signed into a receipt.

Every captured action is described by an ActionDetail: a small, structured record of what the agent is about to do, to which host, under which workflow and account, with which arguments. That record is the input policy reads and the payload that ends up inside the Action Receipt. One action in, one receipt out.

Two things label every action and drive the decision: the verb (what kind of action it is) and the fields map (its arguments). The rest of the loop — policy, approval, redaction, signing — operates on this record.

The seven verbs

The verb is the broad category an action falls into. There are exactly seven, written in lowercase snake_case. Policy rules match on the verb (or on "any"), and several of them cover risky actions — payments, deletes, account changes, large data exports — that carry a pinned floor the engine enforces at load time.

VerbWhat it is
llm_callA model or chat completion.
tool_callA tool or function invocation.
http_requestA raw outbound HTTP call.
paymentMoving money.
data_exportExporting or reading out data.
account_changeChanging an account or config.
deleteA destructive removal.

The ActionDetail record

ActionDetail is the record HESO captures for every action. It sits under content.action in the receipt. The fields below are exactly what a captured action carries — nothing is invented at sign time.

verbVerbrequired
One of the seven verbs. The broad category policy matches on.
tool_namestringrequired
The specific operation, e.g. stripe.transfers.create. Identifies what ran, beyond the verb.
target_hoststring
The outbound host the action reaches, e.g. api.stripe.com. Omitted for llm_call and other internal actions that have no remote host. Policy scope matches against this.
workflowstringrequired
The named workflow the action belongs to, e.g. vendor-payouts. A rule subject can scope to a workflow.
accountstringrequired
The account the action runs under. A rule subject can scope to an account.
fieldsRecord<string,string>required
The action’s arguments, post-redaction — any field marked for redaction is already a hash or removed before this map is built. See the fields map.
result_hashstring
A hash of the action’s result, when one is recorded. Pins the output bytes without storing them.
errorstring
An error string, when the action failed rather than returning a result.

A captured action for an allowed vendor payment looks like this:

action.json
{  "verb": "payment",  "tool_name": "stripe.transfers.create",  "target_host": "api.stripe.com",  "workflow": "vendor-payouts",  "account": "acct_19",  "fields": {    "amount_usd": "4200",    "payee": "Globex LLC",    "member_id": "blake3:7d1a…"  }}

How actions are captured

In Python, the SDK captures the action before it runs. You wrap a function with a decorator, or wrap a client with the proxy, and HESO builds the ActionDetail from the call it intercepts. The gate decision happens on that record; only then does the underlying call proceed.

Capture does two things automatically:

  • Infers the verb from the call. A create on an LLM client becomes llm_call; a request on an HTTP client becomes http_request.
  • Turns kwargs into fields. The call’s keyword arguments become the fields map, so policy can read them by name.
from heso import tool @tool                      # captured before the body runsdef create_invoice(account: str, amount_usd: int):    # verb inferred from the name → tool_call    # kwargs become fields: {"account": ..., "amount_usd": "..."}    return billing.create(account=account, amount_usd=amount_usd)

You can also gate a whole client at once with heso.wrap, which captures its create and request calls as actions in place. The full decorator and proxy surface lives in the Python SDK reference.

The fields map

fields is a flat Record<string,string>of the action’s arguments — and it is the post-redaction map. By the time policy reads it, anything marked for redaction is already a commitment hash or removed, so secrets never sit in plaintext in the record that gets signed and shipped.

This map is what policy conditions evaluate. A condition names a field, picks an operator, and compares against a typed value — alongside the verb and target_host, these are the inputs a rule matches on.

heso.toml
[[rule]]id = "pay-cap"verb = "payment"# reads action.fields.amount_usd off the captured actionconditions = [  { field = "amount_usd", op = "lte", value = 5000, display = "at most $5,000" },]decision = "allow"

Because the map is built post-redaction, a condition reads the redacted value, not the original — so policy can match on the presence or shape of a field without the cloud ever seeing its secret. See conditions & operators for every operator the engine evaluates and how values are typed.

The captured action is what gets signed

The ActionDetail captured here is exactly what lands inside the signed Action Receipt under content.action. The receipt’s action_hash is computed over the canonical bytes of that content, so the verb and fields you see captured are the same bytes anyone can verify later.