Skip to content

Agno Adapter

The AgnoAdapter produces a wrap-around hook function compatible with Agno's tool_hooks parameter. Unlike other adapters, the Agno hook wraps the entire tool execution -- it receives the callable and is responsible for invoking it.

When to use this

Add Edictum to your Agno agent when you need to enforce contracts on tool calls. The as_tool_hook() method returns a wrap-around function that integrates with Agno's tool_hooks parameter, applying preconditions and postconditions to every tool invocation. Because the hook wraps the entire tool execution, it can both deny calls and replace tool results -- making the on_postcondition_warn callback suitable for PII redaction in regulated environments.

Installation

pip install edictum[agno]

Integration

from edictum import Edictum
from edictum.adapters.agno import AgnoAdapter
from agno.agent import Agent

guard = Edictum.from_yaml("contracts.yaml")
adapter = AgnoAdapter(guard=guard)
hook = adapter.as_tool_hook()

agent = Agent(
    model="gpt-4o-mini",
    tools=[search_tool, file_tool],
    tool_hooks=[hook],
)

The hook is passed via the tool_hooks parameter on the Agent constructor. It intercepts every tool call made by the agent.

Hook Behavior

The hook function receives three arguments:

(function_name: str, function_call: Callable, arguments: dict) -> result

The adapter controls the full lifecycle:

  1. Evaluates preconditions against the tool name and arguments.
  2. If allowed, calls function_call(**arguments) to execute the tool.
  3. Evaluates postconditions against the result.
  4. Returns the tool result on success or "DENIED: <reason>" on deny.

Because the adapter wraps the entire call, it can both block execution and replace the returned result.

PII Redaction Callback

Use on_postcondition_warn to transform tool output when postconditions flag issues. Because this is a wrap-around adapter, the callback's return value replaces the tool result:

import re

def redact_pii(result, findings):
    text = str(result)
    text = re.sub(r"\b\d{3}-\d{2}-\d{4}\b", "[SSN REDACTED]", text)
    text = re.sub(r"\b[\w.+-]+@[\w-]+\.[\w.-]+\b", "[EMAIL REDACTED]", text)
    return text

hook = adapter.as_tool_hook(on_postcondition_warn=redact_pii)

Known Limitations

  • Kwargs spread: The adapter calls function_call(**arguments), spreading the arguments dict as keyword arguments. Your tool callables must accept keyword arguments, not a single positional dict.

  • Async-to-sync bridging: Agno hooks are synchronous, but Edictum's pipeline is async. When no event loop is running, the adapter uses asyncio.run(). When a loop is already running (common in async frameworks), it spins up a ThreadPoolExecutor with a single worker to run the async code in a fresh event loop. Objects with thread affinity (e.g., some DB connections) may not transfer correctly across this boundary.

  • Async tool support: If function_call(**arguments) returns a coroutine, the adapter awaits it automatically.

Full Working Example

from edictum import Edictum, Principal
from edictum.adapters.agno import AgnoAdapter
from agno.agent import Agent

# Load contracts
guard = Edictum.from_yaml("contracts.yaml")

adapter = AgnoAdapter(
    guard=guard,
    session_id="agno-session-01",
    principal=Principal(user_id="research-agent", role="analyst"),
)

hook = adapter.as_tool_hook()

# Define tools
def search_documents(query: str) -> str:
    """Search the document store."""
    return f"Found 3 results for: {query}"

def read_file(path: str) -> str:
    """Read a file from disk."""
    with open(path) as f:
        return f.read()

# Build agent with contract enforcement
agent = Agent(
    model="gpt-4o-mini",
    tools=[search_documents, read_file],
    tool_hooks=[hook],
)

result = agent.run("Search for quarterly earnings data")
print(result)

Observe Mode

Deploy contracts without enforcement to see what would be denied:

guard = Edictum.from_yaml("contracts.yaml", mode="observe")
adapter = AgnoAdapter(guard=guard)
hook = adapter.as_tool_hook()

In observe mode, the hook always allows tool calls through, even for calls that would be denied. CALL_WOULD_DENY audit events are emitted so you can review enforcement behavior before enabling it.