Agno, Semantic Kernel, and OpenAI Agents Adapters¶
This page covers the three remaining framework adapters. Each section includes installation, setup, a full example, and framework-specific notes.
All adapters share the same constructor signature and lifecycle described in the Adapter Overview. The differences are in how each framework exposes hook points and what format it expects for allow/deny signals.
Agno¶
The AgnoAdapter produces a wrap-around hook function compatible with Agno's
tool_hooks parameter. Unlike the other adapters, the Agno hook wraps the
entire tool execution -- it receives the callable and is responsible for
invoking it.
Installation¶
Setup¶
from edictum import Edictum, Principal
from edictum.adapters.agno import AgnoAdapter
guard = Edictum.from_yaml("contracts.yaml")
adapter = AgnoAdapter(
guard=guard,
session_id="agno-session-01",
principal=Principal(user_id="agno-agent", role="assistant"),
)
hook = adapter.as_tool_hook()
Full Example¶
from edictum import Edictum, Principal
from edictum.adapters.agno import AgnoAdapter
from agno import Agent
# Configure governance
guard = Edictum.from_yaml("contracts.yaml")
adapter = AgnoAdapter(
guard=guard,
principal=Principal(user_id="research-agent"),
)
hook = adapter.as_tool_hook()
# Pass the hook to Agno's tool_hooks parameter
agent = Agent(
model="gpt-4o-mini",
tools=[search_tool, file_tool],
tool_hooks=[hook],
)
result = agent.run("Look up the latest quarterly earnings")
Hook Behavior¶
The hook function has this signature:
The adapter controls the full lifecycle:
- Runs pre-execution governance.
- If allowed, calls
function_call(**arguments)-- note the kwargs spread. The tool callable receives keyword arguments, not a single dict. - Runs post-execution governance.
- Returns the tool result on success or
"DENIED: <reason>"on deny.
Notes¶
-
Kwargs spread: Agno passes tool arguments as a dict, but the adapter spreads them with
function_call(**arguments). Your tool callables must accept keyword arguments, not a single positional dict. -
Async-to-sync bridging: Agno's
tool_hooksare synchronous, but Edictum's pipeline is async. The adapter detects whether an event loop is already running:- If no loop is running, it uses
asyncio.run(). - If a loop is already running (common in async frameworks), it spins up a
ThreadPoolExecutorwith a single worker and runs the async code in a fresh event loop on that thread.
- If no loop is running, it uses
-
Async tool support: If
function_call(**arguments)returns a coroutine, the adapter awaits it automatically.
Semantic Kernel¶
The SemanticKernelAdapter registers an AUTO_FUNCTION_INVOCATION filter on a
Semantic Kernel Kernel instance. The filter intercepts every auto-invoked
function call and runs Edictum governance around it.
Installation¶
Setup¶
from edictum import Edictum, Principal
from edictum.adapters.semantic_kernel import SemanticKernelAdapter
from semantic_kernel import Kernel
kernel = Kernel()
guard = Edictum.from_yaml("contracts.yaml")
adapter = SemanticKernelAdapter(
guard=guard,
session_id="sk-session-01",
principal=Principal(user_id="sk-agent", role="assistant"),
)
# Register the filter on the kernel
adapter.register(kernel)
Full Example¶
from edictum import Edictum, Principal
from edictum.adapters.semantic_kernel import SemanticKernelAdapter
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
# Build kernel
kernel = Kernel()
kernel.add_service(OpenAIChatCompletion(service_id="chat", ai_model_id="gpt-4o-mini"))
kernel.add_plugin(file_plugin, "FileOps")
kernel.add_plugin(search_plugin, "Search")
# Configure governance
guard = Edictum.from_yaml("contracts.yaml")
adapter = SemanticKernelAdapter(
guard=guard,
principal=Principal(user_id="analyst", role="data-team"),
)
adapter.register(kernel)
# Use the kernel -- all auto-invoked functions are now governed
settings = kernel.get_prompt_execution_settings_from_service_id("chat")
settings.function_choice_behavior = "auto"
result = await kernel.invoke_prompt(
"Summarize the contents of report.txt",
settings=settings,
)
Hook Behavior¶
The adapter registers a filter using @kernel.filter(FilterTypes.AUTO_FUNCTION_INVOCATION).
Inside the filter:
- Extracts
context.function.nameandcontext.arguments. - Runs pre-execution governance.
- On allow: calls
await next(context)to let Semantic Kernel execute the function, then runs post-execution governance oncontext.function_result. - On deny: sets
context.function_resultto the denial string and setscontext.terminate = True. The function is never executed, and the kernel stops further auto-invocations in the current turn.
Notes¶
-
Filter registration:
adapter.register(kernel)must be called before invoking prompts that trigger auto function calls. The filter is permanently registered on the kernel instance. -
Terminate on deny: Setting
context.terminate = Truestops the kernel from making additional function calls in the same turn. The LLM receives the denial message and can decide how to proceed on the next turn. -
Error detection: Beyond standard string-based error checking, the adapter also inspects Semantic Kernel
FunctionResultobjects for error metadata viaresult.metadata.get("error").
OpenAI Agents SDK¶
The OpenAIAgentsAdapter produces a pair of guardrail functions --
(input_guardrail, output_guardrail) -- compatible with the OpenAI Agents SDK's
tool guardrail system.
Installation¶
Setup¶
from edictum import Edictum, Principal
from edictum.adapters.openai_agents import OpenAIAgentsAdapter
guard = Edictum.from_yaml("contracts.yaml")
adapter = OpenAIAgentsAdapter(
guard=guard,
session_id="oai-session-01",
principal=Principal(user_id="oai-agent", role="assistant"),
)
input_guardrail, output_guardrail = adapter.as_guardrails()
Full Example¶
from edictum import Edictum, Principal
from edictum.adapters.openai_agents import OpenAIAgentsAdapter
from agents import Agent
# Configure governance
guard = Edictum.from_yaml("contracts.yaml")
adapter = OpenAIAgentsAdapter(
guard=guard,
principal=Principal(user_id="support-agent", role="tier-1"),
)
input_gr, output_gr = adapter.as_guardrails()
# Build agent with guardrails
agent = Agent(
name="Support Agent",
model="gpt-4o-mini",
tools=[ticket_tool, knowledge_base_tool],
input_guardrails=[input_gr],
output_guardrails=[output_gr],
)
result = await agent.run("Look up ticket SUPPORT-4521 and summarize the issue")
Guardrail Behavior¶
Input guardrail (pre-execution)¶
The input guardrail fires before each tool call. It extracts the tool name and arguments from the guardrail data:
- On allow: returns
ToolGuardrailFunctionOutput.allow(). - On deny: returns
ToolGuardrailFunctionOutput.reject_content(reason).
Output guardrail (post-execution)¶
The output guardrail fires after tool execution. It runs postconditions and
records the execution. The output guardrail always returns
ToolGuardrailFunctionOutput.allow() -- post-execution governance produces
audit events and warnings but does not block the response.
Notes¶
-
FIFO correlation: The OpenAI Agents SDK does not pass a shared
tool_use_idbetween input and output guardrails. The adapter correlates them using insertion-order iteration over its pending dict (next(iter(self._pending))). This works correctly for sequential tool execution but assumes tools are not invoked in parallel within a single agent run. If the SDK ever supports parallel tool calls, a proper correlation key would be needed. -
Input guardrails vs output guardrails: The SDK treats these as separate function types decorated with
@tool_input_guardrailand@tool_output_guardrailrespectively. Both are passed to theAgentconstructor as lists.
Common Patterns¶
These patterns apply to all four adapters on this page.
Observe Mode¶
All adapters support observe mode for safe production rollout:
Denials are logged as CALL_WOULD_DENY audit events but tool calls proceed
normally.
Custom Audit Sinks¶
Route audit output to a file with automatic redaction:
from edictum.audit import FileAuditSink, RedactionPolicy
redaction = RedactionPolicy()
sink = FileAuditSink("audit.jsonl", redaction=redaction)
guard = Edictum.from_yaml(
"contracts.yaml",
audit_sink=sink,
redaction=redaction,
)
Principal for Identity Context¶
Attach identity information to every audit event in a session:
from edictum import Principal
principal = Principal(
user_id="alice",
role="sre",
ticket_ref="JIRA-1234",
claims={"department": "platform", "clearance": "l2"},
)
adapter = SomeAdapter(guard=guard, principal=principal)
The principal is included in every AuditEvent emitted by the adapter. Use it
to trace which user or service triggered each tool call in your audit logs.