Getting started /Quickstart: Python

Quickstart: Python

Gate, sign, and audit a real tool call in about ten lines. You end with a signed receipt you can verify offline.

The Python SDK gates your agent in the same process. No separate service. You wrap a tool, a delete, or an LLM client, and every call it makes is captured, checked against your policy, signed into an Action Receipt, and linked into a BLAKE3 audit chain. The signing runs in the bundled Rust core, so there is no extra process to manage.

The path through this page:

  1. Install the package.
  2. Scaffold a project with heso init.
  3. Run the demo to mint and verify a receipt end to end.
  4. Gate a tool, gate a delete, and redact a field.
  5. Wrap an LLM client so every call is gated.
  6. Run it and see what each call leaves behind.

Install

Install the heso package. It needs Python 3.10 or newer and ships the Rust core as the heso._core wheel, so nothing else compiles.

bash
pip install heso

For the Node and TypeScript SDKs and the browser verifier, see Installation.

Scaffold a project

heso init creates your operator identity and writes a starter heso.toml. It also adds the local data directory to .gitignore. Run it on a fresh machine with no environment setup, and run it again safely. An existing key and policy are left as-is.

bash
heso init
bash
  wrote bootstrap module -> heso_bootstrap.py
  wrote starter policy -> heso.toml
  gitignored heso-local-data/
  generated dev key passphrase -> heso-local-data/DEV-ONLY.passphrase (dev only — set HESO_KEY_PASSPHRASE in production)
  operator identity ready (pubkey z6Mk…)
heso init: project ready at /path/to/your-agent
  next: run `heso demo` to mint and verify your first receipt

You are left with these files:

bash
heso_bootstrap.py     # import heso; heso.init()
heso.toml             # your starter policy (first-match-wins rules)
heso-local-data/      # minted operator key + DEV-ONLY.passphrase + receipts.jsonl (gitignored)

heso_bootstrap.py is a one-line entry point. Import it once at process start:

heso_bootstrap.pypython
import heso

heso.init()
The data directory is local and gitignored

The operator key, the audit log, and the receipts.jsonl store live in heso-local-data/, which heso init adds to your .gitignore. The signing key stays on your machine.

The dev passphrase is for dev only

The operator key is encrypted at rest with a passphrase read from HESO_KEY_PASSPHRASE. To keep you unblocked, heso init auto-generates a dev passphrase into heso-local-data/DEV-ONLY.passphrase, and heso.init() loads it. That file sits next to the key it unlocks, so it adds no real protection. For production, set a real HESO_KEY_PASSPHRASE (it always wins) and delete the dev file. HESO_ENV=production refuses the dev file outright; heso init --require-passphrase enforces that in any environment.

Mint and verify your first receipt

Before touching your own code, heso demo proves the loop. It scaffolds the project if needed, mints one allowed and one redacted receipt through the real engine, verifies both offline, and shows you how to re-verify them.

bash
heso demo
bash
minting your first receipts through the engine:
  allow     7f3c91ab…  ->  Valid (L0)
  redacted  2a8e04dd…  ->  Valid (L0)

  receipts written to /path/to/your-agent/heso-local-data/receipts.jsonl
  verify either yourself, offline:
      heso verify 7f3c91ab…

Each receipt was appended to heso-local-data/receipts.jsonl, one signed Action Receipt per line. Now re-check one yourself. Pass heso verify a stored receipt’s action_hash (a unique prefix works) or a receipt file path:

bash
# by action_hash (full or a unique prefix) from the local store…
heso verify 7f3c91ab

# …or a receipt JSON file straight off disk
heso verify ./receipt.json
bash
  action 7f3c91ab…
  Valid (L0)
  (verified offline — re-ran BLAKE3 + Ed25519 locally, zero network)

The verdict comes back in the same PascalCase the engine returns. Here it is Valid (L0), an operator-signed receipt. heso verify exits 0 only on a Valid verdict and 1 on anything else, so it drops into a script or a CI gate. To see the full signed JSON, use heso show:

bash
heso show 7f3c91ab

Gate a tool

Call heso.init() once to load the active config, then decorate a function with @heso.tool. Each call is captured as a tool_call action, checked against policy, signed, and audited.

agent.pypython
import heso

heso.init()


@heso.tool
def search(query: str) -> str:
    # this body only runs if policy lets the call through
    return web.search(query)


results = search("quarterly revenue, ACME Corp")

When policy blocks a call, the SDK raises BlockedError before the body runs, so the side effect never happens:

agent.pypython
from heso import BlockedError

try:
    search("something policy forbids")
except BlockedError:
    # the tool body never ran — no side effect happened
    ...

Blocking is the default (blocking=True). Set blocking=False for observe-only mode: a refused action is still captured, signed, and audited, but it does not raise and the body still runs.

For destructive operations use @heso.destructive, which gates the call as a delete verb. Deletes ride a pinned floor: a policy can tighten the rule but can never allow a delete without approval.

agent.pypython
@heso.destructive
def delete_record(record_id: str) -> None:
    db.delete(record_id)

Redact sensitive fields

Pass redact to keep named fields off our servers. Listed fields are commit-and-reveal redacted before the receipt is signed: HESO stores a BLAKE3 fingerprint of the value, never the value.

agent.pypython
@heso.tool(redact=["api_key"])
def call_partner_api(api_key: str, endpoint: str) -> dict:
    return requests.get(endpoint, headers={"Authorization": api_key}).json()

Here api_key is committed and stripped; the rest of the call is captured and signed as usual. The original value never reaches HESO. See Redaction for how commit-and-reveal differs from destructive mode and how the markers verify.

Gate an LLM client

To gate a whole client without decorating each call, wrap it with heso.wrap(). It returns a stand-in that gates create calls as llm_call and request calls as http_request, and passes everything else through.

agent.pypython
import heso
from openai import OpenAI

heso.init()

client = heso.wrap(OpenAI())

# gated at the leaf — verb llm_call; the call kwargs become the action fields
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "summarize the filing"}],
)

The stand-in reaches into nested attributes, so client.chat.completions.create(...) is gated at the final call and its arguments become the action fields. If the gate refuses, it raises BlockedError or SuspendedError instead of sending. For wiring OpenAI and Anthropic, see OpenAI & Anthropic.

Run it

Initialize the SDK before your agent runs. The simplest way is to import the bootstrap module at the very top of your entrypoint, so heso.init() runs before your other imports. Calling heso.init() yourself first does the same thing.

main.pypython
import heso_bootstrap  # init before anything else

from agent import run
run()

To tag a group of actions with a workflow label, use heso.step() as a context manager. Every action captured inside the block is scoped to that workflow.

agent.pypython
import heso

heso.init()

with heso.step(workflow="run-42"):
    search("pricing for the enterprise tier")
    call_partner_api(api_key, "https://api.partner.example/v1/quote")

What you get

Each gated call produced a complete, standalone piece of evidence:

  • A signed Action Receipt recording the verb, the tool, the policy verdict, any redaction markers, and the Ed25519 operator signature.
  • A new entry on the BLAKE3 audit chain, where each receipt’s hash links to the one before it. Altering an earlier receipt breaks every link after it.
  • An artifact anyone can verify offline: recompute the hash, check the signature, re-derive the trust level. No HESO infrastructure needed.
What a receipt proves — and what it doesn’t

A 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, not whether the action succeeded downstream.

Now check one. The browser verifier re-runs the same Ed25519 and BLAKE3 math locally, and Action receipts walks the wire shape field by field.

Next steps