Guides · Per-tool billing

A search shouldn't cost what a sandbox costs.

One agent run touches many tools, and they don't cost the same to serve. This playbook prices each tool invocation distinctly on a single channel: a rate card per tool, a tool id on every receipt, receiver-side rate checks, and a per-tool cost report at the end.

1 · Define a rate card per tool

The rate card is the contract: one price per tool id, published by the receiver and pinned by the payer. Keep tool ids stable and versioned — a receipt for tools/code.exec should mean the same thing next month as it does today.

rates.ts · the rate card
export const RATES = {
  "tools/web.search":  "$0.0004",  // cheap, high volume
  "tools/pdf.extract": "$0.0012",
  "tools/vision.ocr":  "$0.0025",
  "tools/code.exec":   "$0.0060"   // sandboxed · 15× a search
}

2 · Tag each receipt with the tool id

On the payer side, the tool id is the receipt's action and the pinned rate rides along. Nothing else about the flow changes: sign locally, fire to the counterparty, keep going.

payer.ts · one receipt per invocation
async function invoke(tool, args) {
  const res = await counterparty.invoke(tool, args)

  const receipt = channel.sign({
    action: tool,            // e.g. "tools/code.exec"
    rate:   RATES[tool],
    units:  1,
    nonce:  channel.next()
  })
  counterparty.send(receipt)

  return res
}

3 · Verify the rate against the card

The receiver never trusts the number on the receipt. Every incoming receipt is checked against its own copy of the card: unknown tool ids are rejected outright, and an off-card rate — stale card, buggy payer, or someone trying their luck — never reaches the tally.

receiver.ts · rate check before tally
receiver.on("receipt", (r) => {
  const card = RATES[r.action]

  if (!card) return receiver.reject(r)           // unknown tool
  if (r.rate !== card) return receiver.reject(r) // off-card rate

  receiver.tally(r)  // value = units × card rate
})

4 · Tally a mixed run on one channel

One channel, many rates. The tally is still a single unpaid balance climbing toward one trust threshold — searches and sandbox runs just contribute different amounts per receipt. Settlement doesn't care either: the RAV aggregates every receipt regardless of rate, and one transaction settles the whole mix.

receiver.ts · one balance, many rates
const run = receiver.tallyFor("agent:planner.v3")

run.balance()                     // $1.42 across every tool
run.balance("tools/code.exec")   // $0.90 of it · 150 receipts
run.balance("tools/web.search")  // $0.28 of it · 700 receipts

5 · Report cost per tool

Because every receipt carries a tool id, cost attribution falls out for free. Group the tally by action and you can see which tools earn their keep, bill your own customers per capability, or hand an agent operator a line-itemed view of where a run's budget went.

receiver.ts · group the tally by tool
const report = receiver.report({ groupBy: "action" })

// tools/code.exec     150 calls   $0.90   63%
// tools/web.search    700 calls   $0.28   20%
// tools/pdf.extract   200 calls   $0.24   17%

6 · Picking the rates

Two honest anchors. Cost-plus: start from what an invocation costs you to serve — compute, licenses, upstream API fees — and add a margin; sandboxed execution lands well above a cached search this way, which is the point. Value-based works only where the output has a measurable price to the caller, and it's easy to overreach with. In practice: cost-plus for commodity tools, value-based sparingly for genuinely scarce capabilities, and revisit the card when your report from step 5 shows a tool consistently under water. Publish changes ahead of time — payers pin rates, so a silent bump just means rejected receipts.

Put a rate card into production

The quickstart covers the receipt loop end to end, and the API reference documents rate pinning, tallying, and reporting.