One PATCH call changed an AI agent's budget from $4,000/month to $5,000, no redeploy, no code change. The agent never found out, because it never held that number to begin with.
Disclosure: I work on Pink Agentic AI Payments at PinkWallet.
Short answer: spin up a free sandbox workspace, set the agent's monthly budget and per-payment cap with that one PATCH call, and connect the agent over MCP. The limit lives on the server, not in the agent's system prompt, so the agent cannot talk its way past it. Below are the exact calls against a live sandbox, with real responses, run today.
Why the limit can't live in the prompt
"Don't spend more than $500/month" in a system prompt is a suggestion the model reads before deciding. An agent with a long context window, a confused retry loop, or a crafted tool response can drift past it, and nothing stops the next tool call from going through anyway. Pink Agentic AI Payments checks every payment an AI agent requests against per-agent rules on the server, before it executes. The agent calls a tool; the server decides; the agent gets back one of three outcomes it did not choose.
Step 1: create a sandbox workspace
No signup, no key exchange beforehand:
curl -s -X POST "https://agentic-sandbox.pinkwallet.com/v1/sandbox/workspaces?utm_source=devto&utm_medium=article&utm_campaign=spending-limit-5min" \
-H 'content-type: application/json' \
-d '{"company":"Acme Robotics","email":"dev@acme-robotics.example","template":"coffee"}'
This returns an admin_key, four agent keys (one per role), and a workspace already seeded with 8 payees and 11 rules. Trimmed real response from the run used for this article:
{
"workspace_id": "u21i7shilt",
"admin_key": "pk_sandbox_admin_u21i7shilt...cee8",
"agents": [
{ "id": "a_purch", "name": "Purchasing AI", "key": "pk_sandbox_agent_u21i7shilt_a_purch_...2ebf", "monthly_budget": 4000, "per_payment_cap": 500 }
],
"payees": ["p_cc", "p_sysco", "p_uline", "p_oatly", "p_meta", "p_gusto", "p_energy", "p_square"],
"rules": 11
}
Save the admin key and the Purchasing AI agent key as environment variables ($ADMIN, $PURCH) for the rest of this walkthrough.
Step 2: set the agent's monthly budget and per-payment cap
One PATCH call on the admin route changes both numbers for a single agent, no redeploy, no code change:
curl -s -X PATCH "https://agentic-sandbox.pinkwallet.com/v1/admin/agents/a_purch" \
-H "authorization: Bearer $ADMIN" -H 'content-type: application/json' \
-d '{"monthly":5000,"perPay":600}'
Real response:
{
"id": "a_purch", "name": "Purchasing AI", "dept": "Kitchen",
"monthly": 5000, "perPay": 600, "spentMonth": 2260, "spentToday": 0,
"status": "active", "trust": 95
}
monthly is the hard ceiling for this agent across the current month; perPay caps any single payment regardless of what the monthly total has left. Both are enforced before a payment is created, not checked afterward. The template also ships a company-wide daily ceiling across every agent in the workspace, visible on GET /v1/budget as company_daily_ceiling.
Step 3: the ask rule and the block rule are already there
The coffee template's rule set includes two rules worth pointing at directly, both evaluated in order, first match wins:
-
r5"Bigger supply orders: store manager checks", anaskrule: payments between $500 and $2,000 to an approved payee route to a named human, "Luis Ortega (Store manager)," instead of executing. -
r11"Never: gift cards, cash-like, crypto", ablockrule with no human path at all. It runs first, ahead of every ask rule, so a gift-card purchase never reaches anyone for approval.
You can add your own payee-, category-, or amount-based rules with PUT /v1/admin/rules (body: {"rules":[...]}, replaces the whole set and bumps policy_version); for this walkthrough the template's existing ask and block rules are enough to show all three outcomes.
Step 4: connect the agent over MCP
Claude Desktop (via the mcp-remote bridge, since this sandbox has no OAuth flow yet):
{
"mcpServers": {
"pink": {
"command": "npx",
"args": ["mcp-remote", "https://agentic-sandbox.pinkwallet.com/mcp", "--header", "Authorization:${PINK_AUTH_HEADER}"],
"env": { "PINK_AUTH_HEADER": "Bearer <YOUR_SANDBOX_AGENT_KEY>" }
}
}
}
Cursor (native url + headers support):
{
"mcpServers": {
"pink": {
"url": "https://agentic-sandbox.pinkwallet.com/mcp",
"headers": { "Authorization": "Bearer ${env:PINK_AGENT_KEY}" }
}
}
}
Either way, the agent gets 7 tools: pink.get_budget, pink.list_payees, pink.list_rules, pink.check_policy, pink.request_payment, pink.get_credential, pink.report_receipt.
Step 5: fire three payments, get three outcomes
Same agent key, same rules, three different amounts and payees. Real, trimmed responses from the sandbox run for this article:
Allowed ($150, under the $600 per-payment cap and under rule r4's $500 ceiling):
curl -s -X POST "https://agentic-sandbox.pinkwallet.com/v1/payments" -H "authorization: Bearer $PURCH" \
-H 'content-type: application/json' -d '{"payee_id":"p_cc","amount":150,"purpose":"5kg espresso beans"}'
{
"decision": "allowed", "rule": "Small supply orders go through",
"credential": { "type": "bank_transfer", "max_amount": 150, "locked_to": "Counter Culture Coffee", "single_use": true, "expires_at": "2026-10-17T11:48:02.100Z" }
}
pending_human ($600, inside r5's $500–$2,000 band):
curl -s -X POST "https://agentic-sandbox.pinkwallet.com/v1/payments" -H "authorization: Bearer $PURCH" \
-H 'content-type: application/json' -d '{"payee_id":"p_uline","amount":600,"purpose":"bulk cup and lid order"}'
{
"decision": "pending_human", "rule": "Bigger supply orders: store manager checks",
"hold_id": "pay_b2c92cc5d182", "who": "Luis Ortega (Store manager)",
"expires_at": "2026-10-17T12:03:02.287Z"
}
Blocked (gift cards, regardless of amount):
curl -s -X POST "https://agentic-sandbox.pinkwallet.com/v1/payments" -H "authorization: Bearer $PURCH" \
-H 'content-type: application/json' -d '{"payee_name":"giftcards.com","amount":50,"purpose":"staff prize gift cards"}'
{ "decision": "blocked", "rule": "Never: gift cards, cash-like, crypto", "credential": null, "why": "matched · block" }
Note the field name on a block is why, not reason. The injected-sounding purpose text ("staff prize gift cards") doesn't change anything either way: the rule matches on the payee category, not on parsing the free-text purpose field, so a prompt-injected purpose can't talk the engine into a different decision.
What happens under parallel calls
A single limit on paper means nothing if ten parallel tool calls can each slip through before the server updates the running total. We tested this directly: 40 parallel $7 payments against an agent with a $200/day rule. Each workspace runs as one Cloudflare Durable Object, and the request handler loads, evaluates, spends, and saves with no other work in between, so the 40 requests get evaluated one at a time even though they arrive at once.
Result from today's run (reproducible with the race_test.sh script in the sandbox-examples repo, example 08-race-test):
Allowed: 28 Blocked: 12
Allowed spend: $196
GET /v1/budget -> "spent_today": 196
28 payments cleared before the $200/day ceiling was reached; the 29th and every one after it in the batch got blocked. Total spend stayed at $196, never over $200, no matter how many requests arrived in the same instant. This is the same result as two earlier independent runs logged against this sandbox, so it isn't a one-off.
Limits of this sandbox, stated honestly
This is a sandbox: test money, test cards (4111 1111... BINs), no production deployment yet (early access is the current path to production). The rule engine checks amount, payee, category, time window, and a short evidence-flag list; it does not independently verify the evidence an agent attaches (a stated invoice number or PO is self-asserted, not cross-checked against an accounting system). There is no count-based rule in this engine today (no "max N payments per day" breaker), only amount- and time-window-based ones. And this sandbox doesn't wrap any other provider's own payment tools; it's a standalone MCP/REST server.
Try it
Sandbox is free, no account required to create a workspace: agentic-sandbox.pinkwallet.com. Full API reference: pinkwallet.com/agentic/developers. Want to generate a starting rule set from plain-language answers instead of writing JSON by hand: the policy generator.
Top comments (1)
The detail that matters most is that the limit lives on the server and the agent never holds the number, since anything an agent can read, a prompt injection can negotiate with. The rule ordering also reads right: the block rule running before every ask rule means a category that should never reach a human can't route to one. Curious about one edge case: when a mid-month raise lands, is it effective immediately for spend that already ran, or does the new budget apply from the next settlement?