本文へスキップ
nomunomu0504.dev

BLOG #AI-AGENTS

spendctl: 支払う AI エージェントに「法人カードと経費規程」を持たせる

Hiroki Nomura 5 MIN READ ai-agents payments x402 typescript
目次

問題

HTTP 402 で課金する API が増え、エージェントが 1 リクエストごとに支払えるようになった。x402 と MPP(Stripe の Machine Payments Protocol)がその筋道だ。ただし買い手側の統制は置き去りになっている。x402 v2 の仕様は「クライアント側の予算管理」を明示的に範囲外とし、SDK のガードレールはエージェント自身のプロセスの中で動き、ウォレット事業者は自分が預かるウォレットしか見ない。公式クライアントが、価格を 10 倍にしたサーバーにそのまま支払った報告もある(x402#3866)。

プロンプトは支出制御にならない。「慎重に使って」と書いても、モデルは説得されれば払う。

作ったもの

spendctl は、エージェントの HTTP をいったん通すローカルのサイドカーだ。売り手が 402 を返したら、価格を読み、エージェントのポリシーに照らし、必要なら人に聞き、あなたのウォレットで 1 回だけ署名し、レシートを台帳に書く。

spendctl.dev のトップページ
spendctl.dev。右は台帳の例で、支払いごとにレシートが 1 行残る
  • エージェントは鍵を持たない。頼むことはできるが、払うことはできない
  • 非カストディアル。署名はあなたが動かすサイドカーの中で、Coinbase CDP のサーバーウォレットか自分の鍵で行う。spendctl が鍵や資金を預かることはない
  • 拒否や保留の理由は、モデルが読める普通の文章で返す
spendctl sidecar between an agent and a paid APIAI agentMCP · SDK · fetchholds no keysrequestspendctl sidecaron your machinereads 402 → checks policysigns once · your walletHTTP 402paid APIthe sellerx402 / MPPprice · payTopolicy.yamlbudgets · allowlistapproval thresholdsledger · SQLiteone receipt per paymentno headers, no bodiesreceipta humanapprove / denyadmin tokenpending_approvalfail-closed: an unknown agent, asset or scheme is denied, with a reason the model can read
エージェントと有料 API の間にサイドカーが入る。鍵と署名はサイドカーの中から出ない

ポリシーは 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 に。

  1. Forecall: MCP ツールの説明文を採点するリンターを作って分かったこと 6 min
Hiroki Nomura @nomunomu0504 · CTO, MIRAI STUDIO

Flutter / AWS / 自宅サーバー。個人開発と OSS をいくつか。設計相談や協業の話は X の DM か About から。 About

COMMENTS · GISCUS