SDK integration
Running a custom Python agent? Route its tool calls through the Guardyx SDK to get the same controls — policy, approvals, audit, observability — around code you already own. The SDK is a thin gate in front of each tool. Every AI action passes through Guardyx. Policy runs before execution.
How reversible that is depends on the pattern you pick. With the adapters below,
governance is additive: your tools keep their existing code, Guardyx wraps them, and
removing the SDK leaves the tools running — you just lose the gate. The @guardyx_tool
decorator is different: it replaces the function body, so the call is executed by the
Guardyx backend through its configured connector rather than by your code. That is the
point of it, but it means the decorated function has no local implementation to fall back
on. Choose it when you want Guardyx to own execution; choose an adapter when you want to
keep your own.
When to choose the SDK vs MCP
Section titled “When to choose the SDK vs MCP”- MCP is the primary path when your agent runs inside an MCP client (Claude Desktop, Claude Code, ChatGPT Desktop, Cursor). It’s a config change with no code edits. See the Quickstart.
- SDK is the right path when you own the agent code and want explicit control over which calls are governed — a LangGraph/LangChain agent, an OpenAI Agents or Claude Agent SDK app, CrewAI, AutoGen, Google ADK, a raw Chat Completions loop, or a fully custom framework.
Install
Section titled “Install”The base package pulls in only httpx and includes every adapter class. Install the matching
optional extra when an adapter needs its framework’s runtime. The raw OpenAI-tools adapter
can handle tool-call JSON with the core install; its extra adds the OpenAI client for making
Chat Completions requests.
pip install guardyx # core client + decorators (httpx only)pip install guardyx[langgraph] # LangGraph / LangChainpip install guardyx[openai-agents] # OpenAI Agents SDKpip install guardyx[claude] # Claude Agent SDK (PreToolUse hooks)pip install guardyx[crewai] # CrewAIpip install guardyx[autogen] # AutoGenpip install guardyx[google-adk] # Google ADKpip install guardyx[openai-tools] # raw tool loops + the optional OpenAI clientHow governance works
Section titled “How governance works”The unit of accountability is a run — one complete agent invocation. A run can make many tool calls; each call is one policy decision and one tamper-evident audit record. Every governed call resolves to one of three decisions:
| Decision | What the SDK does |
|---|---|
allow |
The tool executes and returns its result. |
deny |
GuardyxDeniedError is raised; the tool body never runs. |
require_approval |
The call blocks while a human reviews it in the portal. On approval the tool runs; on denial, expiry, or timeout the SDK raises. |
Tool identities live in the portal, not in your code. You register each tool once (Agents → Tools) and pass its UUID into the SDK. The SDK is stateless — it does not register or discover tools.
Configure the client
Section titled “Configure the client”GuardyxClient takes an agent’s API key and UUID, plus your tenant’s base URL. Both the
key and the host are shown in the portal’s Agent created modal.
import os
from guardyx import GuardyxClient
gx = GuardyxClient( api_key=os.environ["GUARDYX_API_KEY"], # per-agent key from the portal agent_id=os.environ["GUARDYX_AGENT_ID"], # from the portal base_url=os.environ["GUARDYX_BASE_URL"], # e.g. https://your-guardyx-host/v1)Approval polling is configurable per call:
await run.invoke( tool_id="<tool-uuid>", payload={"key": "value"}, poll_interval=2.0, # seconds between approval polls (default 2.0) timeout=300.0, # seconds before GuardyxTimeoutError (default 300))Integration patterns
Section titled “Integration patterns”Pick the pattern that matches how your agent is built. All three route through the same gateway and produce the same audit trail.
Decorator API (custom agents)
Section titled “Decorator API (custom agents)”Best for function-based tools with no agent framework. @guardyx_tool replaces a
function’s body with a governed invoke; @guardyx_agent opens one run for the whole
call and propagates it to every governed tool automatically.
import os
from guardyx import GuardyxClient, guardyx_tool, guardyx_agent
gx = GuardyxClient(api_key=os.environ["GUARDYX_API_KEY"], agent_id="<agent-uuid>", base_url="https://your-guardyx-host/v1")
@guardyx_tool("<tool-uuid>")async def send_slack_message(channel: str, text: str) -> dict: """Body never runs — Guardyx intercepts and routes through policy.""" ...
@guardyx_agent(client=gx)async def hello_agent(channel: str) -> str: result = await send_slack_message(channel=channel, text="Hello from Guardyx! 👋") return f"Message sent to {channel}"One @guardyx_agent call opens one run. Every governed tool call inside that run creates
its own audit record. The tool’s arguments become the payload Guardyx evaluates, so
policies can reason over the exact channel and text.
Framework adapters
Section titled “Framework adapters”Every adapter shares the same shape: construct it with your credentials, wrap your
existing tools with their portal UUIDs, and open a run around the agent. Wrapping is
drop-in — your tools and agent logic are unchanged.
import os
from guardyx import GuardyxLangGraphAdapterfrom langchain_core.messages import HumanMessagefrom langchain_openai import ChatOpenAIfrom langgraph.prebuilt import create_react_agent
adapter = GuardyxLangGraphAdapter( api_key=os.environ["GUARDYX_API_KEY"], agent_id="<agent-uuid>", base_url="https://your-guardyx-host/v1",)
# Wrap existing LangGraph/LangChain tools with their portal tool UUIDs.governed_tools = adapter.wrap_many([ (web_search, "<tool-uuid-1>"), (send_email, "<tool-uuid-2>"), (query_database, "<tool-uuid-3>"),])
async def run_agent(query: str): async with adapter.run(): # one run per invocation agent = create_react_agent(ChatOpenAI(model="gpt-4o"), governed_tools) return await agent.ainvoke({"messages": [HumanMessage(content=query)]})The same adapter.wrap(...) / adapter.run() pattern applies to the other adapters:
# OpenAI Agents SDK — wraps FunctionTool.on_invoke_toolfrom guardyx import GuardyxOpenAIAgentsAdaptergoverned = GuardyxOpenAIAgentsAdapter(...).wrap(my_function_tool, "<tool-uuid>")
# CrewAI — wraps a crewai.tools.BaseToolfrom guardyx import GuardyxCrewAIAdaptergoverned = GuardyxCrewAIAdapter(...).wrap(MyCrewTool(), "<tool-uuid>")
# AutoGen — returns an autogen_core FunctionToolfrom guardyx import GuardyxAutoGenAdaptergoverned = GuardyxAutoGenAdapter(...).wrap(my_callable, "<tool-uuid>")
# Google ADK — returns a google.adk.tools.FunctionToolfrom guardyx import GuardyxGoogleAdkAdaptergoverned = GuardyxGoogleAdkAdapter(...).wrap(my_callable, "<tool-uuid>")Raw OpenAI Chat Completions (no Agents SDK) uses wrap plus a dispatch step. Feed
tools_payload(...) into the model, then dispatch the tool call the model emits — policy
runs before your function does:
import os
from guardyx import GuardyxOpenAIToolsAdapter
adapter = GuardyxOpenAIToolsAdapter(api_key=os.environ["GUARDYX_API_KEY"], agent_id="<agent-uuid>", base_url="https://your-guardyx-host/v1")weather = adapter.wrap(get_weather, "<tool-uuid>")
tools = adapter.tools_payload([weather]) # pass as tools=[...] to the modelasync with adapter.run(): result = await adapter.dispatch(weather, arguments_json) # arguments from the modelClaude Agent SDK integrates through PreToolUse hooks rather than wrapped callables.
Map Claude tool names to portal UUIDs, then hand the hook dict to ClaudeAgentOptions:
import os
from guardyx import GuardyxClaudeSdkAdapter
adapter = GuardyxClaudeSdkAdapter( api_key=os.environ["GUARDYX_API_KEY"], agent_id="<agent-uuid>", base_url="https://your-guardyx-host/v1", tool_name_to_guardyx_id={"Bash": "<tool-uuid>"},)
# from claude_agent_sdk import ClaudeAgentOptions# opts = ClaudeAgentOptions(hooks=adapter.hooks_dict())# ... open `async with adapter.run():` around your ClaudeSDKClient session.Direct client
Section titled “Direct client”For a fully custom framework, drive the run yourself. run.invoke returns the result on
allow and blocks through the approval loop when policy requires it.
async with gx.run() as run: result = await run.invoke("<tool-uuid>", {"param": "value"}) # If policy = require_approval, this awaits until a human acts in the portal.Supported frameworks
Section titled “Supported frameworks”The framework_type you pick when creating an agent in the portal determines the adapter.
Everything below is SDK-consumed — you import an adapter class and wrap your tools.
MCP-client agents (Claude Desktop, Claude Code, ChatGPT Desktop, Cursor) don’t install the
SDK at all; see the Quickstart.
| Portal slug | Pip extra | Adapter |
|---|---|---|
langgraph |
langgraph |
GuardyxLangGraphAdapter |
langchain |
langgraph |
LangChain tools via GuardyxLangGraphAdapter.wrap() |
openai_agents |
openai-agents |
GuardyxOpenAIAgentsAdapter |
claude_sdk |
claude |
GuardyxClaudeSdkAdapter |
crewai |
crewai |
GuardyxCrewAIAdapter |
autogen |
autogen |
GuardyxAutoGenAdapter |
google_adk |
google-adk |
GuardyxGoogleAdkAdapter |
openai_tools |
(none for the adapter; openai-tools adds the OpenAI client) |
GuardyxOpenAIToolsAdapter |
custom |
(none) | @guardyx_tool / GuardyxClient |
Guardyx ships a native LangGraph adapter, and LangChain tools are governed through that same adapter — both are live.
Approvals
Section titled “Approvals”When policy returns require_approval, the call surfaces in the portal with the full
payload and decision context, and the agent waits — the SDK polls until the review
resolves. Your agent code just sees a longer await; it never distinguishes a fast
allow from a human-approved action.
On approval, the backend dispatches the tool through its configured connector and captures
the outcome inline with the decision. The SDK returns that result to your agent. If the
connector’s execution itself fails after approval, the SDK raises
GuardyxApprovalExecutionError rather than returning a partial result.
Handling decisions and errors
Section titled “Handling decisions and errors”Denials and approval outcomes are raised as typed exceptions, all under GuardyxError:
GuardyxError # base — catch this for "any Guardyx outcome"├── GuardyxDeniedError # policy denied the call outright; tool never ran├── GuardyxApprovalDeniedError # a human reviewer denied the approval├── GuardyxApprovalExpiredError # the approval window closed with no decision├── GuardyxApprovalExecutionError # approved, but the connector's execution failed└── GuardyxTimeoutError # polling exceeded `timeout` before any decisionCatch the ones your agent should recover from and let the rest surface:
from guardyx import ( GuardyxDeniedError, GuardyxApprovalDeniedError, GuardyxTimeoutError,)
try: result = await run.invoke("<tool-uuid>", payload)except GuardyxDeniedError: ... # policy blocked it — pick another path or report back to the userexcept GuardyxApprovalDeniedError: ... # a reviewer said noexcept GuardyxTimeoutError: ... # no decision in time — escalate; see the warning below before retryingWire protocol
Section titled “Wire protocol”Adapters and decorators are conveniences over four HTTP calls. If you’re integrating a
framework the SDK doesn’t cover, you can speak the protocol directly. Every request carries
X-API-Key: <agent key>.
| Step | Request | Response |
|---|---|---|
| Start run | POST /v1/runs/start {"agent_id": "..."} |
{"id": "<run-id>"} |
| Invoke tool | POST /v1/runs/{id}/invoke {"tool_id": "...", "payload": {...}} |
{"decision": "allow|deny|require_approval", "result"?, "approval_id"?, "reason"?} |
| Poll approval | GET /v1/approvals/{id} |
{"status": "pending|approved|denied|expired", "invocation": {"status", "result", "error"}} |
| Complete run | POST /v1/runs/{id}/complete |
{} |
Get your credentials and tool IDs
Section titled “Get your credentials and tool IDs”- API key + base URL — the portal’s Agent created modal, shown when you create an agent.
- Tool UUIDs — Agents → Tools. Register each tool your agent calls, then pass its
UUID into
@guardyx_tool(...),adapter.wrap(...), orrun.invoke(...).
Next steps
Section titled “Next steps”- Quickstart (MCP client) — govern an MCP client with a config change, no code.
- Overview — how the runtime control layer governs every tool call.