Adding spending controls to CrewAI agents
CrewAI's role system maps cleanly to policy scopes. Here's how to give one agent in your crew purchasing authority while keeping the others read-only.
CrewAI agents work in teams. When one of those agents can spend money, the whole crew inherits that blast radius unless you draw the line explicitly. Here's how to draw it.
Why does CrewAI need spending controls specifically?
CrewAI's mental model is a crew of specialized agents: a researcher, a writer, a reviewer, a buyer. Each agent has a role, a goal, and a tool belt. That role system is useful — it's also the reason spending controls matter more here than in a single-agent setup.
In a single-agent loop, the blast radius is one model making one call. In a crew, the researcher can hand context to the buyer, the buyer trusts that context, and a prompt injection in a scraped webpage now has a path to your payment rail through three hops of agent-to-agent delegation. The role boundaries look like security boundaries, but by default they are not.
PayGraph is an open-source SDK for policy-controlled spending, approvals, and audit logs for AI agents. It treats CrewAI roles as first-class policy scopes so the buyer agent has real purchasing authority and the researcher agent has none — enforced at the tool layer, not in the prompt.
How do CrewAI roles map to policy scopes?
A CrewAI role is a string label attached to an agent. A PayGraph scope is a named policy binding. The mapping is direct: one role, one scope, one set of rules.
| CrewAI role | Scope | Max per txn | Daily cap | Approval threshold |
|---|---|---|---|---|
researcher | read-only | $0 | $0 | N/A |
buyer | purchasing | $500 | $2,000 | $100 |
ops_lead | ops | $5,000 | $20,000 | $1,000 |
finance_admin | admin | $50,000 | $100,000 | $10,000 |
Scopes are not suggestions. A tool call from the researcher agent that tries to invoke make_payment is blocked before it reaches the payment rail, regardless of what the model was convinced to do by a prompt, a document, or a teammate agent.
A minimal crew with one purchasing agent
Here's a three-agent crew where only the buyer can spend. The researcher gathers vendor options, the reviewer checks them against budget, and the buyer executes. PayGraph enforces the boundary.
from crewai import Agent, Crew, Task
from paygraph import PolicyEngine, Policy, Scope
engine = PolicyEngine(scopes=[
Scope(name="read-only", policy=Policy(max_per_transaction_usd=0)),
Scope(name="purchasing", policy=Policy(
max_per_transaction_usd=500,
daily_cap_usd=2000,
allowed_categories=["software", "ads"],
allowed_vendors=["vercel", "openai", "meta-ads"],
require_approval_above_usd=100,
)),
])
@engine.guarded_tool(scope="purchasing")
def make_payment(amount_usd: float, vendor: str, category: str):
# your Stripe Issuing or x402 call here
...
@engine.guarded_tool(scope="read-only")
def lookup_vendor(name: str):
...
researcher = Agent(
role="researcher",
goal="Find vendor options under budget",
tools=[lookup_vendor],
)
buyer = Agent(
role="buyer",
goal="Execute approved purchases",
tools=[make_payment, lookup_vendor],
)The scope argument on each decorator binds the tool to a policy. When CrewAI's orchestrator tries to route make_payment to the researcher, PayGraph rejects the call and writes the attempt to the audit log. The researcher's prompt does not need to know about the rule.
What does the crew look like at runtime?
The flow from task assignment to executed payment passes through four checkpoints. Every one of them is logged.
- Task dispatch — CrewAI assigns a subtask to an agent. The agent's role determines which scope it operates under.
- Tool invocation — The agent calls a tool. PayGraph checks the active scope against the tool's required scope. A mismatch halts execution and logs the attempt.
- Policy evaluation — For scopes that do have tool access, PayGraph checks amount, vendor, category, daily totals, and time-of-day rules before any money moves.
- Approval or execution — If the transaction exceeds the approval threshold, it routes to a human via webhook or Slack. Otherwise it executes and writes to the immutable audit log.
The audit log entry captures role, scope, agent id, task id, tool name, arguments, policy decision, and outcome. When finance asks which agent bought the $480 ad package last Tuesday, you answer in one query instead of a postmortem. For the broader pattern behind this, see our policy-controlled spending architecture breakdown.
How does this compare to DIY role checks?
Most teams we talk to start with a if agent.role == "buyer": check inside a custom tool wrapper. It works until it doesn't.
| PayGraph scopes | DIY role checks | Prompt-level guardrails | |
|---|---|---|---|
| Enforced at tool layer | Yes | Yes, if you remember | No |
| Survives prompt injection | Yes | Yes | No |
| Per-scope daily caps | Built-in | Custom state store | No |
| Approval routing | Built-in | You build it | No |
| Audit log with scope context | Yes | Usually missing | No |
| Shared across crews | Yes | Copy-paste per repo | N/A |
The DIY approach fails in two ways. First, caps and rate limits require state — you end up writing a Redis layer to track daily totals per role. Second, every new crew rebuilds the same wrapper with small bugs. PayGraph centralizes the rules so a policy change updates every crew at once.
Where to start
- GitHub: github.com/paygraph-ai/paygraph — MIT licensed, with a CrewAI adapter that exposes scopes as role-bound tool decorators.
- Docs: docs.paygraph.dev — the CrewAI quickstart, scope reference, and approval webhook formats.
- Discord: discord.gg/PPVZWSMdEm — bring your crew config and we'll help you map roles to scopes.
If your crew has a buyer agent in production, adding scoped policy enforcement is a same-afternoon change. The failure mode it prevents is not.