Skip to content

agno

agno calls a tool's function with the model's arguments as keywords. Put Airlock on the function itself, or in one of agno's tool_hooks. Not in a pre_hook: agno logs what a pre_hook raises and runs the tool anyway.

Read against agno v3.1.12 (2026-10-29). There is no agno adapter module: the decorator and the hook below are all it takes.

Guard the function

from agno.agent import Agent
from agno.tools import tool

from agent_airlock import Airlock
from agent_airlock.policy import SecurityPolicy

policy = SecurityPolicy(denied_tools=["drop_*"], rate_limits={"*": "100/hour"})


@tool
@Airlock(policy=policy)
def delete_records(table: str) -> str:
    return f"deleted {table}"


agent = Agent(tools=[delete_records])

@tool goes on top. agno reads the tool's parameters from the signature @Airlock keeps, and calls it with the model's arguments as keywords, so every check runs: the policy, ghost arguments, strict types, paths and endpoints, then masking of the result. A refusal comes back as the tool's result, a dict with success: False, block_reason and fix_hints for the model to read, and the function does not run.

A policy check in a tool hook

For tools you cannot decorate, such as a toolkit's methods, use tool_hooks (Agent(tool_hooks=[...]) covers every tool the agent has). A hook gets the tool's function_name, a function_call and the arguments, and the tool runs only if the hook calls function_call(**arguments):

from typing import Any, Callable

from agno.agent import Agent

from agent_airlock.policy import PolicyViolation, SecurityPolicy

policy = SecurityPolicy(allowed_tools=["search_*", "get_*"])


def airlock_policy(
    function_name: str, function_call: Callable[..., Any], arguments: dict[str, Any]
) -> Any:
    try:
        policy.check(function_name)
    except PolicyViolation as refused:
        return f"AIRLOCK_BLOCK: {refused.message}"
    return function_call(**arguments)


agent = Agent(tools=[...], tool_hooks=[airlock_policy])

This checks the policy by tool name: the lists, time windows and rate limits. A tool the policy escalates is refused, since no approver is asked here. The hook does not check the arguments' types or values; decorate the function for that.

Not a pre_hook

agno runs a pre_hook before the tool and catches what it raises. It re-raises only its own AgentRunException (StopAgentRun, RetryAgentRun) and RunCancelledException, which stop or retry the whole run. Anything else, a PermissionError included, is logged as Error in pre-hook callback and the tool runs anyway (agno-agi/agno#11404, open; FunctionCall._handle_pre_hook in v3.1.12). A permission check written there the ordinary Python way does not stop the call.