OpenAI & Anthropic
Wrap your model client once with heso.wrap(). Every completion is captured, checked against your policy, and signed — with no per-call changes.
The Python SDK ships heso.wrap. Pass your client through it once and you get a drop-in replacement: same methods, same return values, same provider exceptions. The model call now runs through the gate first. HESO records it as an action, checks it against your policy, and when the verdict allows, signs the result into a receipt and appends it to the audit chain.
Wrap an OpenAI client
Call heso.init() once at process start, then wrap the client. Keep calling client.chat.completions.create(…) exactly as you do today.
import heso
from 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 the nested namespace to the real method, client.chat.completions.create, and gates it as the verb llm_call. The keyword arguments you pass become the action’s fields, so a policy can read model or any other parameter.
Wrap an Anthropic client
Anthropic works the same way. The wrapper reaches client.messages.create and gates it as llm_call, with the same kwargs-become-fields behavior.
import heso
from 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."}],
)Gate on the model or any field
Because the call kwargs become the action’s fields, a policy rule can match on them. This 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:
[[rule]]
id = "gpt4-needs-approval"
order = 10
enabled = true
subject = { kind = "any" }
verb = "llm_call"
scope = "*"
conditions = [
{ field = "model", op = "regex", value = "^gpt-4", display = "model is a GPT-4 variant" },
]
decision = "require_approval"
approvers = ["oncall"]
sla_minutes = 30The same pattern matches claude-* models, or any other kwarg. For the full operator set, see Conditions & operators.
Handle a refusal
A refused call raises before the request is sent, so it never reaches the provider — no charge, no completion. Two outcomes, two exceptions:
- heso.BlockedError — policy returned
block. The call is refused outright. - heso.SuspendedError — policy returned
require_approval. The call is paused until a human co-signs it; the action is captured and an approval opens. The error carriesaction_hashandrule_id.
import heso
from 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 the call. Nothing was sent to the provider.
fall_back_to_a_cheaper_model()
except heso.SuspendedError as e:
# Policy routed the call to a human. It is paused until someone approves.
# e carries action_hash and rule_id; its message prints the next step.
queue_for_approval(e.action_hash)Blocking is the default. To observe without enforcing — every call still captured, signed, and audited, but a refusal does not raise — initialize with blocking=False:
import heso
# Observe-only: refused calls are still captured, signed, and audited,
# but a block does not raise — the call runs ungated.
heso.init(blocking=False)Gate at the tool level instead
If you would rather gate your own tools than the model client, decorate them with @heso.tool. Each call is captured as a tool_call action, checked against the same policy, and signed. The function body runs only if policy allows.
import heso
heso.init()
@heso.tool # captured as a tool_call action, gated, signed
def lookup_contract(contract_id: str) -> str:
return db.fetch(contract_id) # body runs only if policy allowsWrapping the client and decorating tools compose — use both. For the full decorator surface, see the Python SDK reference.
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 that the action succeeded downstream. HESO records intent and authorization, not the quality of the answer.