CrewAI
In this guide you'll give a DevOps crew a provision_gpus tool that asks a human before spending real money. Python, about ten minutes.
Before you start
- A Confirm account and API key. Sign in, open Dashboard → API Keys, create a key, and copy it (it's shown once).
- An Anthropic API key (CrewAI talks to it through LiteLLM).
- Python 3.10+.
terminal
pip install crewai requests.env / shell
export CONFIRM_API_KEY=cfm_live_...
export ANTHROPIC_API_KEY=sk-ant-...Step 1: the approval helper
Same helper as every integration: create the request, poll until a human decides. Copy it as-is (it's identical to the LangChain guide's file, reuse it if you have it).
confirm_client.py
import os
import time
import requests
BASE = "https://confirm.dev/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['CONFIRM_API_KEY']}"}
def require_approval(summary: str, payload: dict, notify: str, ttl_minutes: int = 60, **fields) -> dict:
"""Create an approval request and block until a human decides."""
res = requests.post(
f"{BASE}/requests",
headers=HEADERS,
json={"summary": summary, "payload": payload, "notify": notify,
"ttlMinutes": ttl_minutes, **fields}, # reasoning, recentActions, agentState
timeout=30,
)
res.raise_for_status()
request = res.json()
while True:
time.sleep(5)
current = requests.get(
f"{BASE}/requests/{request['id']}", headers=HEADERS, timeout=30
).json()
if current["status"] != "PENDING":
return currentStep 2: the gated tool and the crew
CrewAI tools subclass BaseTool. The description is what the agent reads, so it says plainly that approval takes time and denial is a real outcome.
crew.py
from crewai import Agent, Crew, Task, LLM
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
from confirm_client import require_approval
class ProvisionInput(BaseModel):
instance_type: str = Field(description="e.g. a100-80gb")
count: int = Field(description="Number of instances")
class ProvisionGPUs(BaseTool):
name: str = "provision_gpus"
description: str = (
"Provision GPU instances. Requests above $500/day require human approval: "
"the tool blocks until someone decides, and may return a denial with a reason. "
"Do not retry a denied request."
)
args_schema: type[BaseModel] = ProvisionInput
def _run(self, instance_type: str, count: int) -> str:
daily_cost = estimate_daily_cost(instance_type, count) # your pricing logic
if daily_cost <= 500:
return provision(instance_type, count) # your real provisioning
verdict = require_approval(
summary=f"Provision {count} x {instance_type} (~${daily_cost:,.0f}/day)",
payload={"instance": instance_type, "count": count, "dailyCost": daily_cost},
notify="group:platform", # or "vp-eng@company.com" on Hobby
reasoning="The research team's eval run needs this capacity for about a week.",
recentActions=["Read the task brief", f"Estimated cost: ${daily_cost:,.0f}/day"],
)
if verdict["status"] != "APPROVED":
note = verdict.get("resolutionNote") or verdict["status"].lower()
return f"Denied by human reviewer: {note}"
approved = verdict["effectivePayload"] # the human may have lowered the count
return provision(approved["instance"], approved["count"])
infra_agent = Agent(
role="DevOps engineer",
goal="Provision the compute the team needs, at reasonable cost",
backstory="You manage cloud infrastructure and respect spending controls.",
tools=[ProvisionGPUs()],
llm=LLM(model="anthropic/claude-opus-4-8"),
)
task = Task(
description="The research team needs 20 A100 80GB instances for a week-long eval run.",
expected_output="A report of what was provisioned, or why it wasn't.",
agent=infra_agent,
)
crew = Crew(agents=[infra_agent], tasks=[task])
print(crew.kickoff())Step 3: run it and edit the request down
terminal
python crew.pyWhat happens:
- The agent calls
provision_gpuswith 20 instances. That's well over $500/day. - The crew pauses mid-task while the tool polls Confirm.
- The approver's email arrives with the summary and the daily cost. Open it.
- Click Edit payload first, change
"count": 20to"count": 8, note "start smaller, scale if needed", and approve. - The tool executes
effectivePayload: 8 instances, not 20. The crew's final report reflects exactly what was actually provisioned.
CREWS AND TIMEOUTS
A crew task blocks while its tool waits, so keep
ttl_minutes short for interactive runs. For long-running crews, give the task a generous execution timeout or route approval-gated steps through a queue and resume on the webhook.Next steps
- Approval groups: send infra spend to the platform team, not one inbox.
- Webhooks for event-driven flows.
- The API reference for
metadata: attach crew and task IDs so the audit log ties back to your runs.