
AgentOps (Casper)
05.2026 – Present
What I learned
Casper on-chain policy anchoring (GuardRegistry contract), weight-based multisig kill-switches for delegated agent keys, x402 micropayment settlement over CEP-18 tokens, atomic budget-hold reservation before signing.
The problem
A company gives an employee a corporate card, not the bank account — a limit, a list of approved merchants, a receipt per swipe.
AI agents today get the bank account: a private key sitting inside the agent process, buying APIs, pulling paid data, placing trades, pushing transactions. If that agent is tricked, buggy, or simply over-eager, nothing stands between it and the money.
AgentOps is the corporate-card system for AI agents, and Casper is what makes the receipts real.
What it does
AgentOps is a spending firewall that sits in front of AI agents. Before an agent can pay for anything, trade anything, or deploy anything on Casper, it's checked against one company-wide budget and rulebook, the money is reserved, and only then does a signature get produced. Every approval that actually settles is stamped onto Casper through our own contract — live on mainnet.
Intent → Policy check → Budget hold → Fund the agent → Sign
→ Execute → Reconcile what really happened → Anchor the successes to Casper
Proving it end-to-end
The job given to a team of AI agents: "Evaluate a 5 CSPR swap on CSPR.trade. Buy whatever market data you need. Report price, liquidity, slippage, fees, and risk. Wait for me to approve before executing anything." One job, three different ways to spend money.
1. Policy check before touching anything
The agent called casper_guard_policy_check — a dry run, like checking a card limit before you swipe. Six checks in one call: is the agent active, is trading an allowed action, is this swap inside its scope, is the network permitted, is the amount under its per-transaction limit, is it under the org ceiling. Passed. No money moved.
2. Real x402 micropayments settled in WCSPR
Two agents bought market data over Casper's x402 rail. Each paid endpoint returned a real HTTP 402 naming the asset (WCSPR, a CEP-18 token), the recipient, and the price. AgentOps evaluated policy and, only on ALLOW, produced the PAYMENT-SIGNATURE header the service demands.
| Agent | Bought | Paid | Settled on Casper |
|---|---|---|---|
| Trade agent | Order-book depth | 2 WCSPR | e1541674… · block 8434250 |
| Risk agent | Risk score | 3 WCSPR | ac53efe1… · block 8434251 |
Both are real transfer_with_authorization calls on the WCSPR contract, broadcast by the CSPR.cloud hosted facilitator — not simulated. Without the signed header, the service returns 402 and refuses to serve. The block is structural, not advisory.
3. A valid key, refused anyway
The trade agent's working credential was used to try to buy the risk agent's data:
{
"outcome": "DENY",
"reason": "service_not_allowed",
"policy_id": "…@v2",
"allowed_resource_ids": ["svc:order-book"]
}A working key is not the same thing as permission. That's the entire product in one response.
4. Funded just in time, never a standing balance
Each agent transacts from its own Casper account. A fresh one holds no WCSPR and has no purse, so settlement first fails on-chain with error 60001. The fix runs after the budget reservation passes, never before: wrap CSPR, dust-transfer to create the agent's purse, move WCSPR into the agent's account. The agent is funded for exactly what it was just authorized to spend, at the moment it needs it.
5. Money reserved before the "yes"
Like a bank placing a hold before a card charge settles, AgentOps atomically reserves the budget before returning the authorization — the reservation and the solvency check run inside a single script, no gap between "checked" and "acted" for a second agent to slip through.
6. A failure that lost nothing
CSPR.trade returned no transaction hash, twice. AgentOps classified both attempts as terminal failures and released both holds: 0 CSPR of swap principal debited, 0 holds left stuck, 0 failures reported as settled. Saying "yes" is the easy half — real financial autonomy needs money reserved before the signature and an explicit recovery path after an execution you can't confirm.
7. The receipt went on mainnet
The settled authorization was hashed and anchored to the GuardRegistry contract, including a real anchor on Casper mainnet: block 8350189, hash sha256:6a5fce4f…, 0.6806 CSPR gas against a 3 CSPR limit. Only settled authorizations get anchored — denials and failures stay in the off-chain audit trail as signed evidence, so no gas is burned recording things that never moved money.
The kill-switch: enforced by Casper, not by us
Instead of our server holding a key, each agent gets its own weight-1 key on the user's own Casper account. Full lifecycle, proven on testnet:
| Step | Transaction |
|---|---|
| Grant — agent key at weight 1, master raised to 3 | 97c50ddf… |
| Agent transacts completely alone — signed with only the agent's key | 426495a5… |
| Revoke — agent key removed | 0a6cce33… |
The node itself rejects the agent afterward: "invalid associated keys" | (rejected — no hash, by design) |
That last rejection is the payoff: after revocation the agent is stopped at the Casper protocol level, not by our policy. Our backend could be offline, compromised, or gone, and the agent still cannot spend.
One detail worth calling out: the obvious implementation bricks the account. The agent must transact alone (deployment threshold 1) but must never touch key management (threshold 3). Naively adding the agent at weight 1 and raising key-management to 3 leaves master(1) + agent(1) = 2, which can never reach 3 — nobody could ever manage keys on that account again. The grant raises the master's own weight to 3 in the same deploy, so the master alone still clears the bar.
GuardRegistry — the receipt layer
A deliberately tiny contract: anchor_decision, get_anchor, total_anchored. About 55 lines.
- Append-only and immutable by construction — the same hash again is a harmless no-op; a different hash for an existing decision reverts
- History cannot be rewritten by an attacker, by us, or by the contract owner
- Emits an event per anchor, so anchors can be counted from the chain without trusting the database
- Deployed to testnet and mainnet with zero logic changes
- Anchors a hash, not the decision — the audit payload stays off-chain and exportable, the chain holds the tamper-evidence
Contracts:
- GuardRegistry mainnet — package
a003b2c3…· contract3f4e5a7c…0802ce54ccd - GuardRegistry testnet — package
850b5056…a9e7d7ed· contract4aac5aaa…c2b61671
One budget across three Casper rails
Most spending controls work per-wallet or per-transaction. Companies don't work that way — one budget, and it doesn't matter which door the money leaves through.
| Rail | AgentOps checks |
|---|---|
| x402 purchase | Amount · real recipient · service scope · velocity · budget |
| CSPR.trade swap | Amount · slippage · risk label · action scope · budget |
| Casper deploy | Network · contract and action scope · amount · budget |
An agent can't exhaust its allowance buying data and then quietly keep spending the same budget through the trading rail.
Agents never hold a signing key
An agent holds an API key. Signing happens server-side inside a vault, after the policy check. There's nothing for the agent to sign with — so there's no path around the firewall.
Live MCP tools and SDK
Claude Code, or any MCP-compatible agent, can use AgentOps directly:
| Group | Tools | For |
|---|---|---|
| Discover | list_services, legal_context, trade_data | Find paid services, fetch terms, read live market data |
| Decide | policy_check, authorize_payment, authorize_action | Dry-run, or get a real signed authorization |
| Settle & prove | reconcile, decision_status, audit_export | Confirm settlement, anchor to Casper, export audit JSON |
| Govern the fleet | create_agent, attach_trading_flow, revoke_agent | Operator-only: provision, attach policy, hard-stop |
npm i @agops-labs/sdk ships createGuardedFetch — a drop-in replacement for fetch that runs the whole x402 handshake (call → 402 → policy check → attach signed header → retry) in one line. A refusal arrives as a typed PaymentDeniedError carrying the reason and decision ID, not a mystery 402.
Verify it yourself
# The mainnet contract exists and holds real anchors
casper-client query-global-state --node-address https://node.mainnet.casper.network/rpc \
--key hash-a003b2c32c6c7bddbd51d9596a31af72e0cdad62268a3747147d809add7ff629
# A real decision from the running system, anchored on Casper mainnet
casper-client get-transaction --node-address https://node.mainnet.casper.network/rpc \
0287e36aa351d850c6acb460147a6c8d767755cae3cec5dd2fb4aae6ece0478e
# The live MCP server exposes its tools
curl -s -X POST https://casper-agopsserver-production.up.railway.app/v1/casper-guard/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'Stack
Next.js 15 (App Router) · React 19 · TypeScript · Tailwind CSS 4 · Zod · CSPR.click · React Flow — plus the Rust/CEP-18 engine, contracts, and SDK in the companion server repo.
What I'd do differently
Anchoring only settled authorizations (not denials/failures) keeps gas costs down, but it means the on-chain record alone can't prove a denial happened — you have to trust the off-chain audit export for that. A cheap batched anchor of denial hashes would close that gap without materially raising gas spend.
Links
- Live app: aops-casper.up.railway.app
- MCP endpoint:
casper-agopsserver-production.up.railway.app/v1/casper-guard/mcp - x402 demo services: casper-agopsservice-production.up.railway.app
- SDK:
@agops-labs/sdk - Finalist demo: YouTube
- Quarterfinalist demo: YouTube
- GitHub (client): AgenticOperations/Casper-AGOPS.Client
- GitHub (server, contracts, SDK): AgenticOperations/Casper-AGOPS.Server