·5 min read·The PayGraph Team

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 roleScopeMax per txnDaily capApproval threshold
researcherread-only$0$0N/A
buyerpurchasing$500$2,000$100
ops_leadops$5,000$20,000$1,000
finance_adminadmin$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.

  1. Task dispatch — CrewAI assigns a subtask to an agent. The agent's role determines which scope it operates under.
  2. 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.
  3. 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.
  4. 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 scopesDIY role checksPrompt-level guardrails
Enforced at tool layerYesYes, if you rememberNo
Survives prompt injectionYesYesNo
Per-scope daily capsBuilt-inCustom state storeNo
Approval routingBuilt-inYou build itNo
Audit log with scope contextYesUsually missingNo
Shared across crewsYesCopy-paste per repoN/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

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.