Getting started /How heso works

How heso works

Every action your agent takes runs the same four stages, in the same order. Here is each one.

The four stages

When your agent is about to act — call a model, hit an API, move money — the SDK catches the call, checks it against your policy, records the verdict as a signed Action Receipt, and chains that receipt onto the previous one.

  1. Intercept— capture the action before it runs.
  2. Decide— evaluate it against your policy and pick one of four paths.
  3. Sign— sign the verdict and the action bytes, then chain the receipt.
  4. Verify— let anyone re-check the receipt later, offline.

1 · Intercept

The SDK captures the action before it executes. A receipt for an action that already ran proves nothing, so HESO records intent first and lets the call through only if the verdict allows it. Wrapping a function with @heso.tool builds an ActionDetail from the call.

The ActionDetail records what the action is and where it points:

  • verb— the kind of action, one of the seven below.
  • tool_name— the tool or function being called.
  • workflow and account— which agent run and which tenant it belongs to.
  • fields— the call arguments, as a string map.
  • target_host — the host the action reaches (omitted for llm_call and other internal actions).

Every action carries exactly one verb. The set is fixed:

  • llm_call— a model completion.
  • tool_call— a function or tool invocation.
  • http_request— an outbound HTTP call.
  • payment— moving money.
  • data_export— sending data out.
  • account_change— changing an account.
  • delete— destroying a resource.

The minimal setup is three lines: import the SDK, call heso.init(), and decorate the tools you want gated. Every call to a decorated function then runs the loop.

agent.pypython
import heso

heso.init()

@heso.tool
def transfer_funds(payee: str, amount_usd: int) -> str:
    # runs only after policy allows it
    return stripe.transfers.create(payee=payee, amount=amount_usd)

2 · Decide

The policy engine evaluates the action first-match-wins: it walks your ordered rules, and the first rule whose subject, verb, scope, and conditions all match decides the outcome. No rule matches? The default is allow. The check runs locally.

Every decision resolves to one of four paths:

  • allow— the action proceeds as written.
  • block— the action is stopped and never runs.
  • redact— sensitive fields are replaced before the action proceeds.
  • require_approval— the action is routed to a human and waits for a verdict.

A redacted field is replaced by a hash before anything leaves the process; the cleartext never travels to HESO. The dangerous verbs — payment, delete, account_change, data_export— carry a floor that a policy can tighten but never bypass. To write these rules, see Policy & decisions and Policy files.

3 · Sign

HESO commits the verdict. The verdict and the exact action bytes are signed with an Ed25519 operator key, and the action’s BLAKE3 action_hash (64 hex) is chained onto the previous receipt via prev_receipt_hash. Editing any earlier entry breaks every receipt after it. One signed receipt per action forms the audit chain.

An operator-only receipt is L0. When the decision was require_approval, the action pauses until a human acts. An approver co-signs with their own device-held key — the cloud holds no signing key — and the receipt records a second signature. That co-signed receipt is L1.

L1 has two shapes: a single approver, or a k-of-n quorum where several people each sign their own leg, carrying an extra multi_approval block. A quorum is not a higher level. The trust level is derived from which signatures are present, not set by hand. For the mechanics, see Trust levels, Human approval, and The audit chain.

4 · Verify

Verification needs nothing from HESO. Drop a receipt into a browser and the Ed25519 signatures and BLAKE3 hash re-run locally — no network call, no account. The verifier walks a fixed gate order and stops at the first gate that fails, so a tampered receipt is caught the moment its math stops adding up.

The trust level is re-derived on every verify from the signatures that actually pass. A receipt that claims L1 but carries only an operator signature fails with a trust mismatch. See Offline verification for the full gate order, and Verify in the browser to try it.

What it proves

What this proves

A verified receipt proves the operator authorizedthis action under a known policy, and — at L1 — that a person approved it with a device-held key.

It records what was authorized: the decision and who made it. It does not record whether the action succeeded downstream. Authorization is signed; the outcome is a separate question.

Where each stage runs

Three stages run in your process. Intercept, decide, and sign all happen locally inside the SDK, on one Rust core. There is no round-trip to HESO to decide or sign an action. The control plane receives the finished receipt and re-verifies it before accepting it, but it never sits in the path of the decision.

Verification runs wherever the receipt goes. The same Rust core is compiled to Python, Node, and browser WASM, so verdicts and verification are byte-identical on your server or in a reviewer’s browser.

Next steps