User guideDeveloper guideConnect Python agents
Developer guide

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.

bash
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:

bash
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
VariableDefault and meaning
VW_FAIL_MODEopen. When the gateway can't decide, open runs the tool and closed refuses it.
VW_TIMEOUT3. Seconds per gateway request.
VW_APPROVAL_WAIT120. The longest the agent waits for a human approval, in seconds.
VW_APPROVAL_FAIL_MODEclosed. 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.

python
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.

python
from verifywise_agent_control.crewai import enable_crewai_governance

enable_crewai_governance()
crew.kickoff()
akickoff() blocks the event loop
CrewAI tool-call hooks are synchronous, so each check is a blocking request made inside the hook. Under akickoff() that request, and any wait for a human approval, blocks the event loop: nothing else on that loop runs until the gateway answers, or until the approval is decided or VW_APPROVAL_WAIT runs out. If the crew shares a loop with other work, keep VW_APPROVAL_WAIT short or run the crew with kickoff() in a worker thread.

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.

What "not executed" covers in CrewAI
CrewAI does not say why a tool did not start, so one message covers every reason: another pre-tool hook blocked the call, the tool's usage limit was reached, the result came from CrewAI's tool cache, or CrewAI rejected the arguments. A cache hit therefore shows as not executed, with no output. In rare cases the package cannot tell which call ran: the same tool has two calls in flight and its code runs in a thread that carries neither call's context. The call is then recorded as completed with CrewAI's raw result as it is, and that text is never read as a failure or a block.

Microsoft Agent Framework

Add the function middleware to the agent. It wraps every function the agent calls.

python
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.

python
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 answerWhat the package does
allowRuns the tool, then records its output (up to 9 KB) on the call in Activity.
denyDoes 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_requiredWaits 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 keyThe 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 limitedFollows 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 exactlyAlways 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 packageAlways 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.

python
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"}]})
Fail-open by default
If the gateway is unreachable, tools still run, so an outage never stops your agent. Set VW_FAIL_MODE=closed to refuse calls instead. A rejected agent key is always refused.
PreviousConnect any agent
NextGoverning tool calls
Connect Python agents - Developer guide - VerifyWise User Guide