Agno Adapter
The `AgnoAdapter` produces a wrap-around hook function compatible with Agno's
Right page if: you are adding Edictum to an Agno agent via the tool_hooks parameter. Wrong page if: you are using a different framework -- see https://docs.edictum.ai/docs/adapters/overview to find your adapter. Gotcha: this is a wrap-around adapter, so on_postcondition_warn can replace tool results (suitable for PII redaction). Tool callables must accept keyword arguments -- the adapter spreads args with **arguments.
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.
Getting Started
Install
pip install edictum[agno]Create adapter
from edictum import Edictum
from edictum.adapters.agno import AgnoAdapter
guard = Edictum.from_yaml("contracts.yaml")
adapter = AgnoAdapter(guard=guard)
hook = adapter.as_tool_hook()Wire into agent
from agno.agent import Agent
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) -> resultThe adapter controls the full lifecycle:
- Evaluates preconditions against the tool name and arguments.
- If allowed, calls
function_call(**arguments)to execute the tool. - Evaluates postconditions against the result.
- 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 aThreadPoolExecutorwith 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.
Last updated on