spendctl: 支払う AI エージェントに「法人カードと経費規程」を持たせる
目次
問題
HTTP 402 で課金する API が増え、エージェントが 1 リクエストごとに支払えるようになった。x402 と MPP(Stripe の Machine Payments Protocol)がその筋道だ。ただし買い手側の統制は置き去りになっている。x402 v2 の仕様は「クライアント側の予算管理」を明示的に範囲外とし、SDK のガードレールはエージェント自身のプロセスの中で動き、ウォレット事業者は自分が預かるウォレットしか見ない。公式クライアントが、価格を 10 倍にしたサーバーにそのまま支払った報告もある(x402#3866)。
プロンプトは支出制御にならない。「慎重に使って」と書いても、モデルは説得されれば払う。
作ったもの
spendctl は、エージェントの HTTP をいったん通すローカルのサイドカーだ。売り手が 402 を返したら、価格を読み、エージェントのポリシーに照らし、必要なら人に聞き、あなたのウォレットで 1 回だけ署名し、レシートを台帳に書く。

- エージェントは鍵を持たない。頼むことはできるが、払うことはできない
- 非カストディアル。署名はあなたが動かすサイドカーの中で、Coinbase CDP のサーバーウォレットか自分の鍵で行う。spendctl が鍵や資金を預かることはない
- 拒否や保留の理由は、モデルが読める普通の文章で返す
ポリシーは YAML 1 枚で書く。
agents:
researcher:
budget: { perRequest: "0.05", daily: "1.00", monthly: "20.00" }
onBudgetExceeded: approve # 超えたら拒否ではなく人に聞く
requireApprovalAbove: "0.50"
allow: ["api.weather.dev", "*.search.dev/v1/*"]
payTo:
"api.weather.dev": ["0x2096…287C"] # 受取先のすり替えを拒む
知らないエージェント、知らない資産、対応していないスキーム。どれも理由付きで拒否する。すべてフェイルクローズだ。
守らせること
ルールはそれぞれ、模擬の売り手・模擬のファシリテータ・サイドカーを本物の HTTP で立てたエンドツーエンドのシナリオになっていて、CI のたびに 2 回ずつ走る。いま 22 本。抜粋すると、
- 予算を超える支払いは決して署名されない。ちょうど上限までは通る
- 同時リクエストでも 1 件たりとも超過しない。予算は署名の前に SQLite の
BEGIN IMMEDIATEで予約する - 受取先が固定されたホストで別の受取先を名乗る 402 は拒む。価格が前回の数倍に跳ねたら人に回す
- 同じ
Idempotency-Keyのリトライは二重に払わない - 決済に失敗した署名済みの支払いは黙って再送しない。そのホストへの支払いを止める
- エージェントは自分の支払いを承認できない。承認には管理トークンが要る
- 保留中の支払いを、不要になったエージェント自身が取り下げられる。遅れて来た承認は何も払わない
つなぎ方
どの言語からでも使えるようにゲートウェイ URL を基本にした。サイドカーの URL に本来のリクエストを送り、ヘッダーで本当の宛先とエージェント名を伝える。応答には決定(settled / denied / pending_approval …)と金額がヘッダーで付く。
その上に薄いクライアントがある。TypeScript の @spendctl/client(wrapFetch、依存ゼロ)、Python の spendctl-sdk、そして MCP サーバー。Claude Code なら 1 行だ。
claude mcp add spendctl -e SPENDCTL_AGENT=researcher -- npx spendctl mcp
ログイン不要のサンドボックス https://mcp.spendctl.dev/mcp を MCP クライアントにつなぐと、鍵もチェーンもなしに、模擬の有料 API を相手に拒否と承認の流れを試せる。
決めたこと
- サイドカー本体はプロプライエタリ、クライアントとワイヤプロトコルは Apache-2.0 で公開(miraistudio/spendctl-sdk)。プロトコルはヘッダー数個と小さな JSON なので、他の言語のクライアントは表を見れば書ける
- 支払いの経路は利用者のネットワークから出ない。開発中のコントロールプレーンが同期するのはポリシー・承認・台帳だけで、署名を中継することはない
- 台帳はヘッダーもボディも保存せず、クエリ文字列も落とす
- 支出額の何 % という料金は取らない
状態
v0.7.3。x402 v1 / v2(EVM の exact)は Base Sepolia で実決済まで確認した。MPP は 402 の解析・ポリシー・レシートまでで、実決済はこれから。Solana、upto スキーム、HTTP_PROXY モードでの HTTPS 傍受もまだだ。詳細は spendctl.dev/docs に。
RELATED
Flutter / AWS / 自宅サーバー。個人開発と OSS をいくつか。設計相談や協業の話は X の DM か About から。 About