Start free

OpenAI & Anthropic

Wrap an LLM client so every completion is captured, policy-gated, signed, and audited — without changing how you call the model.

The Python SDK ships heso.wrap. It wraps your client so model calls pass through a gate first, and forwards everything else untouched. You keep calling client.chat.completions.create(…) exactly as you do today. HESO catches the call, records it as an action, runs it through your policy, and — when the verdict allows — signs the result into a receipt and appends it to the audit chain.

Wrap the client

Call heso.init() once at process start, then pass your client through heso.wrap(). The returned object is a drop-in replacement: same methods, same return values, same exceptions from the provider. The only difference is that the model call now passes through the gate first.

agent.py
import hesofrom openai import OpenAI heso.init()client = heso.wrap(OpenAI()) resp = client.chat.completions.create(    model="gpt-4o",    messages=[{"role": "user", "content": "Summarize this contract."}],)

The wrapper reaches through nested namespaces to the actual call — client.chat.completions.create(…) — and gates it there as an action with the verb llm_call. The keyword arguments you pass become the action’s fields, so a policy can read model or any other parameter.

What gets gated

heso.wrap gates two method names and forwards everything else untouched:

  • create is gated as an llm_call — this is the completion / message call on OpenAI and Anthropic clients.
  • request is gated as an http_request — the lower-level transport call, for clients that expose one.

The wrapper reaches through nested namespaces to the real method — client.chat.completions.create on OpenAI, client.messages.create on Anthropic — and gates it there. Every keyword argument you pass (model, temperature, and the rest) is recorded as a field on the action, which is what lets a policy match on the model or any other parameter. See Actions & verbs for the full shape of what gets captured.

Gate on model or fields

Since the call kwargs become the action’s fields, a policy rule can match on them. Here a rule sends any GPT-4 variant to a human approver before the request goes out — the engine reads the model field straight off the captured call:

heso.toml
[[rule]]id = "gpt4-needs-approval"order = 10enabled = truesubject = { kind = "any" }verb = "llm_call"scope = "*"conditions = [  { field = "model", op = "matches", value = "gpt-4*", display = "model is a GPT-4 variant" },]decision = "require_approval"approvers = ["oncall"]sla_minutes = 30

The matches operator does glob matching against the field value, so gpt-4* covers gpt-4o, gpt-4-turbo, and the rest. You can match on any kwarg the same way. For the full operator set and how values are typed, see Conditions & operators.

Handle a block

When policy returns block (or a suspended decision), the wrapped call raises heso.BlockedError instead of sending the request, so it never reaches the provider. Catch it where you want a fallback:

agent.py
import hesofrom openai import OpenAI heso.init()client = heso.wrap(OpenAI()) try:    resp = client.chat.completions.create(        model="gpt-4o",        messages=[{"role": "user", "content": "..."}],    )except heso.BlockedError:    # Policy blocked this call. The request never left your process.    fall_back_to_a_cheaper_model()

This is the default, blocking behavior. If you want to observe without enforcing — every call still captured, signed, and audited, but a block does not raise — initialize with blocking=False:

agent.py
import heso # Observe-only: refused actions are still captured, signed, and audited,# but a block does not raise — the body runs ungated.heso.init(blocking=False)
Blocked before the wire

A BlockedError means the model call was refused at the gate, in-process. Nothing was sent to OpenAI or Anthropic, so there is no charge and no completion. With blocking=Falsethe call proceeds, but the refusal is still recorded in the receipt’s policy outcome.

Anthropic

The same wrap works for an Anthropic client. Its create — reached at client.messages.create — is gated as an llm_callexactly like OpenAI’s, and its kwargs become the action fields, so the model-matching policy above works for claude-* models too:

agent.py
import hesofrom anthropic import Anthropic heso.init()client = heso.wrap(Anthropic()) resp = client.messages.create(    model="claude-sonnet-4-20250514",    max_tokens=1024,    messages=[{"role": "user", "content": "Summarize this contract."}],)
What the receipt proves

A signed completion receipt proves the operator authorized this model call under a known policy — and, at L1, that a human approved it with their own key. It does notprove the model’s output was correct or truthful; that isn’t derivable from the artifact. HESO records intent and authorization, not the quality of the answer.

Next steps