Under the hood

Every action runs the
same 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 receipt, and chains that receipt onto the last one. Same order, every time.

01
Stage 01

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: what the action is and where it points.

verb
the kind of action — one of seven, fixed
tool_name
the tool or function being called
workflow · account
which agent run, and which tenant
fields
the call arguments, as a string map
target_host
the host the action reaches
agent.pypython
import heso

heso.init()

@heso.tool
deftransfer_funds(payee: str, amount_usd: int) -> str:
    # runs only after policy allows it
    return stripe.transfers.create(payee=payee, amount=amount_usd)
Every action carries exactly one verb
llm_calltool_callhttp_requestpaymentdata_exportaccount_changedelete
02
Stage 02

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.

A redacted field is replaced by a hash before anything leaves the process — the cleartext never travels to HESO. The dangerous verbs carry a floor a policy can tighten but never bypass.

Every decision resolves to one of four paths
allowthe action proceeds as written
blockthe action is stopped and never runs
redactsensitive fields are hashed first
require_approvalrouted to a human, waits for a verdict
03
Stage 03

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 is chained onto the previous receipt via prev_receipt_hash. Edit any earlier entry and every receipt after it breaks.

An operator-only receipt is L0. When the decision was require_approval, a person 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. The level is derived from which signatures are present, not set by hand.

One signed receipt per action
action_hash · blake3b3:7d2b…04af
signature · ed255193a9f…e1c0
prev_receipt_hash9c14…2b7a
L0operator signature only
L1operator + a human who co-signed on their device
04
Stage 04

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.

The verifier walks a fixed gate order
Algorithm recognizedalg ok
action_hash recomputedblake3 match
Signatures valided25519 ok
Trust level re-derivedL1 ✓
One byte changedHashMismatch
What it proves

Authorization, signed.

A verified receipt proves the operator authorized this 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

Decide locally. Verify anywhere.

Three stages run in your process. Intercept, decide, and sign all happen locally on one Rust core — no round-trip to decide or sign. The control plane re-verifies the finished receipt, but never sits in the path of the decision.

Intercept · Decide · Sign — in your process
Verify — Python, Node, browser WASM, byte-identical
Read the spec

See it stage
by stage.

The docs walk every stage with the full receipt schema, the policy language, and SDKs for Python, Node, and the browser.

ed25519blake3offlineone rust core