Connect Python agents
Govern the tool calls of LangChain, CrewAI, Microsoft Agent Framework or plain Python agents with the Python package.
Connect Python agents
The verifywise-agent-control package connects a Python agent to Agent Control with one line. Every tool call the agent makes is checked by the gateway before it runs, and its result is recorded after. It has adapters for LangChain, CrewAI and Microsoft Agent Framework, plus a decorator for agents you built yourself.
The package behaves the same way as the bundled hook script used for Claude Code and Cursor. It reads the same environment variables, uses the same defaults and calls the same endpoints, so guardrail rules and approval rules apply. The agent key's allowed and blocked tool lists apply too when the key has "Also apply tool lists to native tool calls" turned on on the Agent keys page.
1. Install the package
The package is installed from the VerifyWise GitHub repository; it is not on PyPI. Add the extra for your framework in the square brackets: langchain, crewai, agent-framework, or all. Leave the brackets out for the core package and the decorator only. Python 3.10 or later is required.
pip install "verifywise-agent-control[langchain] @ git+https://github.com/verifywise-ai/verifywise.git@sdk-py-v0.1.0#subdirectory=sdks/python/verifywise-agent-control"The package is MIT licensed, so you can ship it inside your own agent.
2. Set the environment variables
Create an agent key in AI Gateway > Agent Control > Agent keys, then set:
export VW_GATEWAY_URL=https://your-verifywise-host # the URL you open VerifyWise at
export VW_AGENT_KEY=sk-mcp-... # your agent key
# Local development: the dev server does not forward /v1, so use the backend, e.g. VW_GATEWAY_URL=http://localhost:3000| Variable | Default and meaning |
|---|---|
| VW_FAIL_MODE | open. When the gateway can't decide, open runs the tool and closed refuses it. |
| VW_TIMEOUT | 3. Seconds per gateway request. |
| VW_APPROVAL_WAIT | 120. The longest the agent waits for a human approval, in seconds. |
| VW_APPROVAL_FAIL_MODE | closed. On approval timeout or expiry, closed refuses the call and open runs it. |
Every setting can also be passed in code, for example AgentControlClient(gateway_url=..., agent_key=..., fail_mode="closed"). Values passed in code win over the environment.
3. Add it to your agent
LangChain
Add the middleware to create_agent. It governs every tool the agent calls, including tools added by other middleware.
from langchain.agents import create_agent
from verifywise_agent_control.langchain import VerifyWiseMiddleware
agent = create_agent(
model="openai:gpt-4.1",
tools=[search, send_email],
middleware=[VerifyWiseMiddleware()],
)CrewAI
Call enable_crewai_governance once before kickoff. It registers CrewAI tool-call hooks, which run on every tool call of every crew and agent in the process.
from verifywise_agent_control.crewai import enable_crewai_governance
enable_crewai_governance()
crew.kickoff()For each allowed CrewAI call, the package watches the tool's own code run, and records the outcome from that, never from the text the tool returned. A tool's output can echo a web page, a file or user input, including CrewAI's own "blocked" or "error" wording, so reading that text could make a call that ran look blocked or failed. A call is recorded as completed with its output when the tool returned, as failed with the exception when it raised, and as not executed when the tool never started. A tool that returns a CrewAI ToolFailure is recorded as failed, since that is the tool's own report that it did not do the job.
Microsoft Agent Framework
Add the function middleware to the agent. It wraps every function the agent calls.
from agent_framework import Agent
from verifywise_agent_control.agent_framework import VerifyWiseFunctionMiddleware
agent = Agent(
client=chat_client,
tools=[search, send_email],
middleware=[VerifyWiseFunctionMiddleware()],
)Your own agent
For an agent without a supported framework, decorate the functions it calls. Regular and async functions both work.
from verifywise_agent_control import governed
@governed
def delete_customer(customer_id: str) -> str:
...
@governed(name="crm.lookup")
async def lookup(email: str) -> dict:
...What happens on each decision
| Gateway answer | What the package does |
|---|---|
| allow | Runs the tool, then records its output (up to 9 KB) on the call in Activity. |
| deny | Does not run the tool. The model gets "Blocked by VerifyWise policy: <reason>" as the tool result and the agent continues, so it can explain or try something else. |
| approval_required | Waits and polls every 2 seconds, up to VW_APPROVAL_WAIT. Approved runs the tool, denied refuses it, and expiry or timeout follows VW_APPROVAL_FAIL_MODE. |
| Monitor only key | The decision is always allow, so the tool runs and its output is recorded. When a rule matched, the package logs one warning saying what would have happened, and Activity shows the call as Would block or Would need approval. |
| Gateway down, timing out, erroring (5xx) or rate limited | Follows VW_FAIL_MODE. By default the tool runs. |
| Agent key rejected (401 or 403) | Always refuses the call, whatever VW_FAIL_MODE says, with "VerifyWise rejected the agent key (revoked, expired or invalid)". Revoking a key stops every agent that uses it. |
| Arguments that JSON can't represent exactly | Always refuses the call without contacting the gateway, with "arguments could not be sent to VerifyWise for checking". Arguments are never converted, so the rules check exactly what the tool runs with. Pass dates, decimals and other objects as strings or numbers. |
| An error inside the package | Always refuses the call and logs an error. A bug in the package is not a gateway outage, so it never lets a call through unchecked. |
To stop the agent instead of handing the model a refusal, create the client with on_deny="raise". A refused call then raises VerifyWiseDenied, and the run ends.
When a tool raises an error, the error is recorded and the exception is left to your framework, as if the package were not there. LangChain and the decorator re-raise it; CrewAI and Agent Framework turn it into an error message for the model, as they normally do.
Grouping calls into a run
Tool calls are grouped into runs automatically: per LangGraph thread_id in LangChain, per kickoff in CrewAI and per session in Agent Framework (when you pass session= to agent.run). To choose the run id yourself, wrap the work in vw_session.
If your model calls also go through the VerifyWise AI Gateway, send the same id in the x-vw-agent-run-id header on them. Model calls and tool calls then show up together in one run.
from verifywise_agent_control import vw_session
with vw_session() as run_id:
# send run_id as the x-vw-agent-run-id header on your model client
agent.invoke({"messages": [{"role": "user", "content": "Email the weekly report"}]})