Start free

Python — heso

The heso Python package gates your agent in-process: decorators and a transparent proxy capture each action, run it through the Rust engine, and raise before an unauthorized side effect can fire.

heso is the gating SDK — decorators and a proxy that sit between your agent and the side effects it wants to take. It bundles the Rust core as the heso._core wheel, so every gate runs in-process: no subprocess, and no crypto rewritten in Python. The same core powers the Node and browser surfaces, so a verdict is byte-identical wherever it runs.

This page is a reference. If you are starting from scratch, read the Python quickstart first, then keep this open. For the command-line tools shipped alongside the package, see the CLI reference.

Install

The package is on PyPI and requires Python 3.10 or newer. It bundles the Rust core as the heso._core wheel, so there is no separate binary to install.

bash
pip install heso

Installing heso also installs the heso console script — documented in the CLI reference. To scaffold a project (mint an operator identity and write a starter heso.toml), run heso init.

Initialize

Call heso.init() once at process start. It resolves and installs the active configuration; every decorator, the proxy, and heso.process read from it. Gating needs no binary, since the Rust core ships as an in-process wheel.

heso.init()function

Resolve and install the active configuration. Calling init again replaces the active config.

python
heso.init(*, project_root=None, binary=None, workflow=None,          account=None, clock_override=None, timeout=None,          blocking=None) -> Config

Parameters

project_rootstr | None= None
Where to discover heso.toml and the local data directory. Falls back to the HESO_PROJECT_ROOT environment variable, then discovery.
binarystr | None= None
Path to the Rust engine binary. Not required for gate operations (those use the in-process wheel). Falls back to HESO_BIN.
workflowstr | None= None
Default workflow label stamped onto each action. Falls back to HESO_WORKFLOW. Scope a block of actions to a different label with heso.step.
accountstr | None= None
Default account label for captured actions. Falls back to HESO_ACCOUNT.
clock_overridestr | None= None
Pin the captured-at clock (useful for deterministic tests). Falls back to HESO_CLOCK.
timeoutfloat | None= None
Gate timeout. Falls back to HESO_TIMEOUT.
blockingbool | None= True
When True (the default), a blocked or suspended action raises so the decorated body never runs ungated. When False, HESO observes only: the action is still captured, signed, and audited, but a refusal does not raise. Falls back to HESO_BLOCKING.

ReturnsConfig

Config — the resolved, installed configuration.

Example

bootstrap.py
import heso # Layering: explicit args > env vars > heso.toml > defaults.cfg = heso.init(    workflow="vendor-payouts",    account="acct_19",    blocking=True,  # raise on a blocked/suspended action)
Configuration layering

Each value resolves in order: an explicit keyword argument wins, then the matching environment variable (the HESO_* names above), then a value read from heso.toml discovery, then the built-in default.

Decorators

Decorators are the common way to gate. Each wraps a function so the call is captured as an action, checked against policy, and signed into a receipt before the body runs. With blocking on (the default), a blocked or routed action raises instead of running.

@heso.tooldecorator

Gate a tool call. The action verb is tool_call. The redact form applies commit-and-reveal redaction to the named fields before signing, so the cleartext never leaves the process.

python
@heso.tool                    # verb = tool_call@heso.tool(redact=["api_key"])  # commit-and-reveal redaction first

Parameters

redactlist[str]
Field names to redact before signing, using commit-and-reveal so a commitment is recorded in the receipt while the value stays local. See Redaction.

Example

tools.py
import heso heso.init() @heso.tooldef transfer(payee: str, amount_usd: int) -> str:    # The call is captured as a tool_call, evaluated against policy,    # and signed into a receipt before this body runs.    return stripe.transfers.create(destination=payee, amount=amount_usd) # Redact named fields (commit-and-reveal) before signing.@heso.tool(redact=["api_key"])def call_vendor(api_key: str, endpoint: str) -> dict:    return vendor.request(endpoint, key=api_key)

@heso.destructivedecorator

Gate a delete. The action verb is delete. Deletes are a dangerous lane with a pinned floor, so an unmatched delete routes to a human approver by default.

python
@heso.destructive   # verb = delete

Example

cleanup.py
import heso heso.init() @heso.destructivedef delete_member(member_id: str) -> None:    # verb = delete. Dangerous lanes carry a pinned floor, so an    # unmatched delete routes to a human approver by default.    db.members.delete(member_id)

@heso.actiondecorator

The lower-level decorator: you assemble an Action yourself instead of inferring the verb and fields. Reach for it when the higher-level decorators don’t fit.

python
@heso.action   # assemble an Action yourself

Example

export.py
import hesofrom heso import Action, Verb heso.init() @heso.actiondef export_report(rows: list[dict]) -> str:    # Assemble the Action yourself when a decorator's defaults    # don't fit — this is the lower-level building block.    action = Action(verb=Verb.data_export, tool_name="reports.export",                    fields={"row_count": str(len(rows))})    outcome = heso.process(action)    return write_csv(rows)

Scoping

Use heso.step to tag a block of actions with a specific workflow label — for example, a single agent run — without threading the label through every call.

heso.step()context manager

A context manager that scopes the actions inside it to a given workflow label. Actions captured within the block carry that workflow in their receipt.

python
with heso.step(workflow="run-42"):    ...

Parameters

workflowstrrequired
The workflow label applied to every action captured inside the block.

Example

run.py
import heso heso.init() with heso.step(workflow="run-42"):    transfer("Globex LLC", 4200)   # scoped to workflow "run-42"    notify_finance()               # same workflow label

Wrapping clients

heso.wrapreturns a transparent proxy around a client. It gates the calls that produce side effects and forwards everything else unchanged, so you can keep using the client’s own API.

heso.wrap()function

Wrap a client in a transparent gating proxy.

python
heso.wrap(client) -> proxy

Parameters

clientobjectrequired
The client instance to wrap (for example, an OpenAI or Anthropic client, or an HTTP client).

Returnsproxy

A proxy that gates the client’s side-effecting calls and forwards everything else verbatim.

Example

agent.py
import hesofrom openai import OpenAI heso.init() client = heso.wrap(OpenAI()) # .create at the leaf is gated as an llm_call; the call kwargs# become the action fields. Everything else forwards verbatim.resp = client.chat.completions.create(    model="gpt-4o",    messages=[{"role": "user", "content": "summarize Q3"}],)

The proxy maps calls to verbs and recurses into nested namespaces:

  • create is gated as an llm_call.
  • request is gated as an http_request.
  • It recurses into nested namespaces, so client.chat.completions.create(...) is gated at the leaf.
  • The call keyword arguments become the action fields.
  • When the gate refuses, the proxy raises BlockedError or SuspendedError instead of sending.

For a full walkthrough of gating an LLM client, see OpenAI & Anthropic.

Imperative API

Beneath the decorators sits heso.process — the escape hatch for building and gating an action by hand. It requires that heso.init() has run first.

heso.process()function

Capture, evaluate, and sign a single action you assembled yourself. Requires init.

python
heso.process(action: Action) -> Outcome

Parameters

actionActionrequired
The action to gate. Build it with the Action type and a Verb.

ReturnsOutcome

An Outcome describing the decision (its kind is an OutcomeKind).

Example

manual.py
import hesofrom heso import Action, Verb, OutcomeKind heso.init() action = Action(verb=Verb.payment, tool_name="stripe.transfers.create",                fields={"amount_usd": "4200", "payee": "Globex LLC"}) outcome = heso.process(action)if outcome.kind is OutcomeKind.BLOCKED:    raise RuntimeError("policy blocked this payment")

Suspend / resume

For long-running agents, an action routed to a human pauses instead of failing. The suspend/resume layer lets you capture an action, save its action hash, wait for a decision elsewhere, and then resume. These are the values and callables it exposes:

  • configure — set up the suspend/resume behavior for the active config.
  • gated — wrap a call so it participates in the suspend/resume flow.
  • gate and gate_async — gate an action, synchronously or in an async context.
  • resume — continue a previously suspended action once a decision exists.
  • decision and append_decision — read the decision for a suspended action, or record one.
  • current_action_hash — the action hash of the action currently in flight, used to correlate a suspension with its later resume.
  • The values Gate, ResumeOutcome, SUSPENDED, DENIED, Paused, and ContextLost — the result types and sentinels the layer returns.
Routing to a human

A suspended action is one your policy routed to an approver. The approver co-signs with their own device-held key, producing an L1 receipt. See Human approval for the full flow.

Errors

The package raises a small, typed set of exceptions. The first two are how a refusal surfaces when blocking is on; the last two are setup and bridge failures.

ExceptionRaised when
BlockedErrorPolicy blocked the action and blocking is on, so the decorated body never runs.
SuspendedErrorThe action was routed to a human approver and is suspended pending that decision.
BridgeErrorThe call into the in-process Rust core failed.
HesoConfigErrorConfiguration could not be resolved — for example, a missing or malformed heso.toml, or a gate used before heso.init().

Public types you’ll import alongside these include Config, Action, Outcome, OutcomeKind, Verb, and RedactStrategy.

LangChain

To gate tool calls inside a LangChain agent, pass heso.HesoCallbackHandler (lazily imported) as a callback. The handler intercepts tool invocations and runs them through the same gate as the decorators. The full setup is in the LangChain guide.