Payment Governance for CrewAI Agents
CrewAI is a natural fit for multi-agent systems where several agents collaborate on a task. It's also a natural fit for a common failure mode: one agent in the crew gets the ability to spend money, and suddenly every agent in the crew inherits that capability through delegation. This guide shows how to wire xBPP into a CrewAI crew so every agent operates under a shared, declarative policy.
pip install crewai vanar-xbppxBPP is a policy engine - it evaluates transactions locally and synchronously. It does not call any external service and does not depend on CrewAI.
The simplest CrewAI integration is one agent with a payment tool. Put the policy check inside the tool's _run method.
from crewai_tools import BaseTool
from pydantic import BaseModel, Field
from xbpp import evaluate
import json
with open('policies/crew-agent.json') as f:
POLICY = json.load(f)
class PayInput(BaseModel):
amount: float = Field(..., description="Amount in USDC")
recipient: str = Field(..., description="Recipient address or URL")
class PayTool(BaseTool):
name: str = "pay"
description: str = "Send a USDC payment to a recipient"
args_schema: type[BaseModel] = PayInput
def _run(self, amount: float, recipient: str) -> str:
verdict = evaluate(
{"amount": amount, "currency": "USDC", "recipient": recipient},
POLICY
)
if verdict.decision == "BLOCK":
return json.dumps({
"status": "blocked",
"reasons": verdict.reasons,
"message": verdict.message
})
if verdict.decision == "ESCALATE":
approved = human_approval.request({
"amount": amount,
"recipient": recipient,
"verdict": verdict.to_dict()
})
if not approved:
return json.dumps({"status": "declined_by_human"})
tx = execute_payment(amount, recipient)
return json.dumps({"status": "sent", "tx_hash": tx.hash})Attach PayTool() to whichever Agent needs payment capability. CrewAI's LLM will call it like any other tool; xBPP evaluates every call before money moves.
CrewAI's multi-agent model means any agent in a crew can end up calling a tool indirectly. If you want the same governance rules to apply everywhere, load the policy once and reuse it:
from crewai import Agent, Task, Crew
from xbpp import evaluate
import json
POLICY = json.load(open('policies/crew-agent.json'))
def make_pay_tool(agent_name: str):
"""Create a scoped pay tool that tags the agent in the policy log."""
class ScopedPayTool(BaseTool):
name: str = "pay"
description: str = "Send a USDC payment"
args_schema: type[BaseModel] = PayInput
def _run(self, amount: float, recipient: str) -> str:
verdict = evaluate(
{
"amount": amount,
"currency": "USDC",
"recipient": recipient,
"agent": agent_name # metadata carried through audit log
},
POLICY
)
# ... same handling as before
return ScopedPayTool()
researcher = Agent(
role="Researcher",
goal="Buy data reports on demand",
tools=[make_pay_tool("researcher")]
)
buyer = Agent(
role="Buyer",
goal="Purchase API credits",
tools=[make_pay_tool("buyer")]
)
crew = Crew(agents=[researcher, buyer], tasks=[...])Now both agents share the same policy - same daily budget, same counterparty rules, same escalation thresholds - but each tool call is tagged with which agent made it. Your audit log becomes a clean per-agent accounting of who spent what and why.
One of CrewAI's signature features is agent delegation - an agent can ask another agent to do part of a task. This is where ad-hoc spending controls tend to break down: agent A thinks it's delegating "research" but agent B ends up paying for things, and A's constraints never propagate.
With xBPP at the tool level, this failure mode goes away. Agent B's payment tool evaluates the shared policy regardless of who called it. You don't need to trace the delegation chain and hope constraints transfer - the policy runs at the point of execution, which is the only place that matters.
Three patterns we see in production crews:
One policy file, one daily cap shared across all agents. Simplest to reason about. Best for crews working on a single shared objective (e.g. research team buying reports for one client).
Each agent gets its own daily cap and a shared crew-level ceiling. A researcher gets $500/day but the whole crew collectively can't exceed $2,000/day. This prevents one agent from unilaterally draining the crew budget.
Each CrewAI Task carries its own policy, loaded when the Task runs. Good for crews that handle projects with different budget envelopes - a $50-budget personal task and a $50,000 client project can run on the same crew code with different policy files.
def build_task(name: str, budget_file: str) -> Task:
policy = json.load(open(f'policies/{budget_file}.json'))
# bind the policy to this task's tools
...CrewAI doesn't have a first-class "pause for approval" primitive, but xBPP's ESCALATE verdict gives you the hook. Wire human_approval.request() to whatever your team uses - Slack, a dashboard, email, a CLI prompt during dev. As long as it's synchronous (or awaited), the CrewAI tool call will block on it correctly.
For production, a lightweight pattern:
Under 50 lines of glue code and your crew has real human-in-the-loop governance.
Yes. Hierarchical crews still call tools at leaf agents, which is where xBPP lives. The hierarchy doesn't change anything.
Yes. Load a separate policy file for each agent's tools, or use the shared-with-agent-tag pattern above.
No. Delegation happens at the Crew level; xBPP runs at the tool level. They don't interfere.
Yes. The API mirrors the TypeScript version - same policy format, same verdict shape.