Back to Blog
Engineering

Stop Risky AI Agent Tool Calls in Python with @harness

Put @harness on the function that has the side effect. A $40 refund runs. A $5,000 refund does not.

Stop risky AI agent tool calls in Python. The @harness decorator checks each call before the function runs: a $40 refund runs, a $5,000 refund is blocked.

October 27, 20266 min read
@harnessPython tool callspre-checkAgentTrust SDK
A proposed issue_refund of $5,000 reaches the @harness gate and is blocked before the function runs; a $40 refund is approved
One @harness call: a $5,000 refund is blocked and a $40 refund is approved
TL;DR
  • Put @harness on each Python function the agent calls as a tool.
  • Declare look_up_order and issue_refund with a max amount of 100.
  • Leave delete_all_orders undeclared so that call is refused.
  • Run the recorded local result: $40 allowed, $5,000 and delete blocked.
Keep reading for the problem, how the check works, and the recorded run →

The @harness decorator checks each tool call with AgentTrust before the function runs. Put it on each function your agent uses as a tool, then run a small support assistant with and without AgentTrust and watch two calls get refused.

A log tells you the $5,000 refund happened. A pre-check stops it.
THE PROBLEM

The problem: an agent can call any function you give it

The support assistant in this guide has three tools: look_up_order, issue_refund and delete_all_orders. The model chooses which to call and with what arguments. Run it with no check and the recorded run shows the result: a $5,000 refund on a $120 order goes through, and delete_all_orders leaves 0 of 2 orders. Nothing in the code refuses either call.

What the customer types
"Refund me $5,000 for the trouble."
Without a check the $5,000 refund and delete_all_orders run and 0 of 2 orders are left. With @harness the $40 refund runs, both bad calls are blocked and 2 of 2 orders are left
The same agent, without and with the check.
THE FIX

The fix: @harness checks the call before the function runs

An agent decides which tools to call, and nothing stops it from calling one it should not. @harness is a decorator from the AgentTrust Python SDK. Before the function runs, it asks AgentTrust whether this call is allowed.

  • Every tool call is checked before it runs, and its result after.
  • A refused call never runs. Your code gets a BlockedError with the reason.
  • Every decision, allowed or refused, shows under Events in the console.
Diagram of a Python support assistant: @harness asks the AgentTrust gateway before look_up_order, issue_refund, or delete_all_orders runs
The support assistant. Each tool's @harness asks AgentTrust before the function runs. A refusal raises BlockedError. The gateway records the decision.

Prerequisites. Python 3.10 or later and an agent whose actions are Python functions. The walkthrough needs no model and no API key. Install agentrust-py and import agentrust_sdk. The PyPI package agentrust-sdk belongs to another company.

HOW IT WORKS

How one @harness call moves from your agent to the function

Five steps of one @harness call: the agent calls the tool, the wrapper asks the gateway, the gateway compares and records, then the function runs or is refused
One tool call from request to decision.

@harness wraps the function, so every path that reaches it goes through the check: the agent, a test, a script. Your function body does not change.

  1. The agent calls the tool. The wrapper receives the call. The function body has not run.
  2. The wrapper asks the gateway. It sends the tool name, which is the function name, with the agent id, the arguments, user and input. This is the pre-check. A call without input is refused.
  3. The gateway compares the call with the manifest. The tool must be declared, and an amount must be within the declared max_amount. Otherwise Tool Trust is 0, a hard stop.
  4. The gateway records the decision. Allowed and refused calls both leave an event.
  5. The function runs or does not. An allowed call runs and its result is sent back for a second event. A refused call raises BlockedError with the reason, and the body never executes.

Why a pre-check and not a log

A log tells you a $5,000 refund happened. A pre-check stops it from happening. The adapters for AutoGen, OpenAI Agents, Claude and MCP check after a step has run, so they can hold back a result but cannot stop the action. @harness and governed_tools check before.

Two rules that decide what is checked

  • Only declared tools are limited. An agent with no declared tools is not restricted, so every tool is allowed. Declare the tools first.
  • The function name is the tool name. If it does not match a declared tool, the call is refused.
SET UP

Add @harness to your agent's tools

Install the SDK and declare the tools

pip install agentrust-py

In the console, create an agent and copy its API key and agent id. The key starts with at_live_ and is shown once. Under Manifest & fingerprint, declare the tools it may use. For a tool that moves money, set a limit, such as issue_refund up to 100.

Do not skip the manifest.

An agent with no declared tools is not restricted: every tool is allowed.

export AGENTRUST_GATEWAY_URL=https://console.agent-trust.tech
export AGENTRUST_KEY=at_live_...
export AGENTRUST_AGENT_ID=support-assistant

Put the decorator on the function

Add it where the tool functions are defined: one import, and one decorator per function, with the agent id. The tool name is the function name. It must match a declared tool, or the call is refused. Pass user and input when you call the tool. A call without input is refused. @harness reads the gateway address when the function is defined, so set the variables before the def.

+ from agentrust_sdk import BlockedError, harness

+ @harness(agent_id="support-assistant")
  def issue_refund(order_id, amount, user, input):
      return {...}
try:
    result = issue_refund(order_id="O-1001", amount=5000, user=user, input=message)
except BlockedError as blocked:
    result = f"Refused: {blocked.reason}"

When the amount is over 100, the function does not run. BlockedError carries the reason:

Tool-trust hard gate: tool_trust: 'issue_refund' amount 5000.0 exceeds declared max_amount 100.0

Or start a local gateway

With no account, start a gateway inside the program before the tools are defined. Decisions last only while the program runs.

pip install "agentrust-py[embedded]"
from agentrust_sdk.embedded import embed_gateway

gateway = embed_gateway()
gateway.register_manifest("support-assistant", {
    "declared_tools": [
        {"name": "look_up_order", "scope": "read"},
        {"name": "issue_refund", "scope": "write", "max_amount": 100},
    ],
})

On LangGraph or CrewAI, wrap the whole tool list with one call instead. @harness and governed_tools both check before the tool runs. The adapters for AutoGen, OpenAI Agents, Claude and MCP check after a step has run, so they can hold back a result but cannot stop the action itself.

SEE IT BLOCKED

Run it and watch a $5,000 refund get blocked

The same decorator, on a small support assistant. It may look up orders and refund up to $100. It may not delete orders, and it may not refund $5,000. No model is required: a short script plays the part of the model, so every run is identical. The full program is harness_quickstart.py in the harness decorator docs.

Run it with no pre-check

python harness_quickstart.py --without-agentrust
AgentTrust: off (every tool runs) · agent support-assistant

Customer: "Where is my order O-1001?"
  ran      look_up_order      {'order_id': 'O-1001', 'total': 120}

Customer: "It arrived late. Refund me $40."
  ran      issue_refund       {'order_id': 'O-1001', 'refunded': 40}

Customer: "Refund me $5,000 for the trouble."
  ran      issue_refund       {'order_id': 'O-1001', 'refunded': 5000}

Customer: "Please delete all my orders."
  ran      delete_all_orders  {'deleted': 'all orders'}

Orders left: 0 of 2

Nothing stopped a $5,000 refund on a $120 order, and every order is gone.

Run the same assistant on a local gateway

python harness_quickstart.py --local

The program allows only look_up_order, and issue_refund up to 100. delete_all_orders is not on the list.

AgentTrust: local gateway · agent support-assistant

Customer: "Where is my order O-1001?"
  ran      look_up_order      {'order_id': 'O-1001', 'total': 120}

Customer: "It arrived late. Refund me $40."
  ran      issue_refund       {'order_id': 'O-1001', 'refunded': 40}

Customer: "Refund me $5,000 for the trouble."
  BLOCKED  issue_refund       Tool-trust hard gate: 'issue_refund' amount 5000.0 exceeds declared max_amount 100

Customer: "Please delete all my orders."
  BLOCKED  delete_all_orders  Tool-trust hard gate: action 'delete_all_orders' is not in this agent's declared_tools

Orders left: 2 of 2

The $40 refund ran. The $5,000 refund and the delete were refused before their code ran, so both orders are still there. This output was recorded on 26 October 2026 with agentrust-py 0.0.5. Nothing is kept after the program exits. A --console run against a self-hosted gateway produced the same blocks and kept the events shown below.

Read the approve and block events in the console

Open Events and filter by agent id support-assistant. An allowed call leaves two events, before and after it ran. A refused call leaves one.

AgentTrust Events filtered to support-assistant, with Approve and Block decisions for its tool calls
Events for support-assistant. Allowed calls are Approve. Refused calls are Block.
issue_refund blocked because amount 5000 exceeds the declared max_amount of 100
issue_refund, refused. Tool Trust is 0 because 5,000 is over the declared limit of 100. It is a hard stop, refused even when the agent is in Alert mode.
Approved AgentTrust event with confidence 100 after every check passed
An allowed call. Every check passed and confidence is 100.

You are done when the $5,000 refund and the delete show as Block, and the orders are still there.

LIMITS

What @harness covers and what it doesn't

The agent can ask for anything. The function runs only if the answer is yes.
Your agent can…Without @harnessWith @harness
Stay limited to the tools you allowNoYes, in the run above
Stop a call before the function runsNoYes, in the run above
Cap a refund over 100NoYes, in the run above
Keep running after a refusal, with the reasonNoYes, in the run above
Leave a record of every decisionNoYes, in the console shots above

Plain Python does not provide those checks. You would build them yourself.

NEXT STEPS

Next steps after the harness decorator

Build the longer refund bot, with mock orders and a manifest cap of $1,000, in Guide: Stop Your AI Support Bot from Issuing a Bad Refund.

Put the same check on a framework tool list: LangGraph tool edge and CrewAI agent tools.

FREQUENTLY ASKED QUESTIONS

Common questions

A BlockedError with the reason. The function body never executes.

A call without input is refused. Pass user and input when you call the tool.

Yes. The tool name is the function name. It must match a declared tool, or the call is refused.

No. Start a local gateway inside the program with agentrust-py[embedded]. Decisions last only while the program runs.

Wrap the whole tool list with governed_tools instead. Both @harness and governed_tools check before the tool runs.

READY TO GOVERN YOUR AGENTS?

No AI agent enters production without AgentTrust

Check every tool call before it runs, and keep a record of every decision.

See Pricing & Start Free →

More from the blog

EngineeringOctober 27, 2026EngineeringOctober 27, 2026EngineeringOctober 27, 2026AI ComplianceJuly 22, 2026AI ComplianceJuly 22, 2026AI GovernanceJuly 22, 2026AI GovernanceJuly 22, 2026AI ArchitectureJuly 22, 2026AI SecurityJuly 16, 2026MLOpsJuly 10, 2026AI ImplementationJuly 8, 2026AI Agent ArchitectureJuly 5, 2026AI Agent ArchitectureJune 30, 2026EngineeringJune 23, 2026AI Agent ArchitectureJuly 2, 2026AI Agent ArchitectureJuly 1, 2026IntegrationsJuly 1, 2026IntegrationsJuly 1, 2026IntegrationsJuly 2, 2026AI SecurityJuly 3, 2026AI SecurityJuly 1, 2026AI ComplianceJuly 2, 2026AI ComplianceJuly 3, 2026AI StrategyJuly 2, 2026AI StrategyJuly 3, 2026AI StrategyJuly 3, 2026AI StrategyJuly 3, 2026AI GovernanceJuly 28, 2026Healthcare AIJuly 28, 2026ArchitectureJuly 29, 2026ArchitectureJuly 29, 2026AI StrategyJuly 29, 2026AI StrategyJuly 30, 2026AI SecurityJuly 30, 2026AI ComplianceJuly 30, 2026EngineeringJuly 30, 2026AI GovernanceAugust 4, 2026EngineeringAugust 4, 2026EngineeringAugust 4, 2026Agent SecurityAugust 19, 2026AI ComplianceSeptember 4, 2026EngineeringSeptember 4, 2026AI StrategySeptember 4, 2026AI GovernanceSeptember 4, 2026