One retry, and your agent refunds the same order twice.
Solenoid counts refunds per order, outside the agent. Allow one per order, and the second refund is refused before it runs. It only stops tools that ask Solenoid first.
npx @solenoid.systems/cli init support-bot Run it in your project's root. No email, no card: it creates your account, writes your bot's key to .env and saves your admin key for the CLI.
30 spends hit a limit of 7 at once. Exactly 7 got through.
A recorded run of Solenoid's API code, one client per spend.
- spend 1 recorded as rcp_2
- spend 2 recorded as rcp_3
- spend 5 recorded as rcp_6
- spend 3 recorded as rcp_4
- spend 4 recorded as rcp_5
- spend 6 recorded as rcp_7
- spend 7 recorded as rcp_8
- spend 9 refused: 402 limit_exceeded
- spend 10 refused: 402 limit_exceeded
- spend 11 refused: 402 limit_exceeded
- spend 12 refused: 402 limit_exceeded
- spend 13 refused: 402 limit_exceeded
- spend 14 refused: 402 limit_exceeded
- spend 15 refused: 402 limit_exceeded
- spend 16 refused: 402 limit_exceeded
- spend 17 refused: 402 limit_exceeded
- spend 18 refused: 402 limit_exceeded
- spend 19 refused: 402 limit_exceeded
- spend 20 refused: 402 limit_exceeded
- spend 21 refused: 402 limit_exceeded
- spend 22 refused: 402 limit_exceeded
- spend 23 refused: 402 limit_exceeded
- spend 24 refused: 402 limit_exceeded
- spend 8 refused: 402 limit_exceeded
- spend 25 refused: 402 limit_exceeded
- spend 26 refused: 402 limit_exceeded
- spend 27 refused: 402 limit_exceeded
- spend 28 refused: 402 limit_exceeded
- spend 29 refused: 402 limit_exceeded
- spend 30 refused: 402 limit_exceeded
Test: e2e/test/concurrency.test.ts. Recorder: e2e/record-race.ts.
One call, in front of the action
At the limit, spend throws and the refund never runs. It only limits tools that call it first.
import { solenoid } from '@solenoid.systems/sdk'
const sol = solenoid() // reads SOLENOID_KEY from the environment
export async function refund(order: string, amount: number) {
await sol.spend(`support-bot/${order}`, { refunds: 1 }) // throws LimitExceeded at the limit
await payments.refund(order, amount)
}npx @solenoid.systems/cli limit support-bot refunds=1 --per child
npx @solenoid.systems/cli spend support-bot/o-1042 refunds=1A signed receipt for every recorded spend
This one came from the production API. Anyone with the public keys can check it offline.
{
"id": "rcp_1",
"seq": 1,
"kind": "spend",
"scope": "support-bot/o-1042",
"body": {
"refunds": 1
},
"at": "2026-10-07T23:42:12.784Z",
"kid": "k1",
"prev": "0000000000000000000000000000000000000000000000000000000000000000",
"hash": "a50bc9cc4cec91e064c5f864a5b44ead9f09fbfc54db320e125bc5e7529aebb1",
"sig": "9DQOypAney1PFtkabQriNlW_cTA289uuLxM-JLasnv4LiM9Ryg7UDuno_pBjwLp1tQb1Y9KYFK7zKgsTegTHCQ",
"replay": false
}import { verifyChain, type Receipt } from '@solenoid.systems/sdk'
import saved from './receipt.json' with { type: 'json' } // the receipt above
import wellKnown from './solenoid.json' with { type: 'json' } // saved once from https://api.solenoid.systems/.well-known/solenoid.json
const receipt = saved as Receipt
const { keys } = wellKnown
await verifyChain([receipt], keys) // true, with no network callAny action your code can count
Emails work the same way: emails=3 --per child gives each customer three. To cap model costs, keep your gateway for that.
Per-order limits, in the docs. Test: e2e/test/concurrency.test.ts.
When Solenoid is down, refunds stop
Every limit fails closed by default. A limit set to --on-outage open lets actions through, unrecorded.
Free for 100,000 spends a month, then spends are refused until the month turns. Pro is $29 USD a month for 2M spends.
See pricingA limit the agent can't talk past has to live outside it
An agent can keep acting after its owner says stop. I built Solenoid to be that limit. The agent's spend key can't change a limit. Keep the admin key, which can, out of your support bot's environment.
My note on the /why pageGive your agent its first limit
-
1. In your project's root, run this.
support-botnames your agent, so use your own.npx @solenoid.systems/cli init support-botIt creates your account with no signup form and writes a spend key to
.envasSOLENOID_KEY. It also saves the admin key, which sets every limit, to~/.config/solenoid/credentials, where the CLI reads it. Keep a copy somewhere safe. -
2. Give your coding agent this prompt. If you renamed
support-bot, rename it here too.Read https://solenoid.systems/llms.txt and add Solenoid to this project. I already ran init: the spend key for the scope support-bot is in .env as SOLENOID_KEY. Before every refund the support bot issues, call spend at support-bot/<order id> with { refunds: 1 }, and before every email it sends, call spend at support-bot/<customer id> with { emails: 1 }. Don't catch spend's error around the refund or the send: let it stop the action. Then tell me which npx @solenoid.systems/cli limit commands to run, each with --per child so every order and every customer gets its own count. -
3. Run the limits it suggests, such as
npx @solenoid.systems/cli limit support-bot refunds=1 --per child, which allows one refund per order.
Once your tools call spend, npx @solenoid.systems/cli limit support-bot refunds=0 stops every refund from the next call on. Step 3's command resumes them.