SOLENOID.SYSTEMS

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.

Next: the prompt that has your coding assistant add it

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.

7 recorded, 23 refused
  1. spend 1 recorded as rcp_2
  2. spend 2 recorded as rcp_3
  3. spend 5 recorded as rcp_6
  4. spend 3 recorded as rcp_4
  5. spend 4 recorded as rcp_5
  6. spend 6 recorded as rcp_7
  7. spend 7 recorded as rcp_8
  8. spend 9 refused: 402 limit_exceeded
  9. spend 10 refused: 402 limit_exceeded
  10. spend 11 refused: 402 limit_exceeded
  11. spend 12 refused: 402 limit_exceeded
  12. spend 13 refused: 402 limit_exceeded
  13. spend 14 refused: 402 limit_exceeded
  14. spend 15 refused: 402 limit_exceeded
  15. spend 16 refused: 402 limit_exceeded
  16. spend 17 refused: 402 limit_exceeded
  17. spend 18 refused: 402 limit_exceeded
  18. spend 19 refused: 402 limit_exceeded
  19. spend 20 refused: 402 limit_exceeded
  20. spend 21 refused: 402 limit_exceeded
  21. spend 22 refused: 402 limit_exceeded
  22. spend 23 refused: 402 limit_exceeded
  23. spend 24 refused: 402 limit_exceeded
  24. spend 8 refused: 402 limit_exceeded
  25. spend 25 refused: 402 limit_exceeded
  26. spend 26 refused: 402 limit_exceeded
  27. spend 27 refused: 402 limit_exceeded
  28. spend 28 refused: 402 limit_exceeded
  29. spend 29 refused: 402 limit_exceeded
  30. 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=1

A 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 call

Checking receipts offline, in the docs.

Any 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.

Outage settings, in the docs.

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 pricing

A 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.

The two keys, in the docs.

My note on the /why page

Give your agent its first limit

  1. 1. In your project's root, run this. support-bot names your agent, so use your own.

    npx @solenoid.systems/cli init support-bot

    It creates your account with no signup form and writes a spend key to .env as SOLENOID_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. 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. 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.