Guides · Per-milestone billing
Long multi-step tasks shouldn't be billed by call volume — a worker can burn ten thousand calls and deliver nothing. This playbook gates each payment tranche on a verifiable milestone: escrow the full budget up front, run an acceptance check, and sign a receipt only when the check passes.
A milestone is only as good as its acceptance check. Each one needs a criterion the payer can evaluate mechanically — a measurable result, not "looks done". If you can't write the check as code, split the milestone until you can.
const MILESTONES = [ { id: "corpus", tranche: "$40" }, // 10k pages crawled + deduped { id: "index", tranche: "$35" }, // recall ≥ 0.9 on the eval set { id: "report", tranche: "$25" } // every claim cites the corpus ]
The worker needs proof the money exists before committing hours of compute, and the payer needs proof it can't be drawn without receipts. Escrow gives both: lock the sum of all tranches, gated by a thawing period. The same primitive backs multi-agent jobs — see swarm escrow & thawing.
const escrow = await payer.escrow({ to: "agent:index-builder.v1", amount: "$100", // sum of all tranches thaw: "72h" // undrawn balance returns after thawing }) const channel = await payer.openChannel({ to: "agent:index-builder.v1", escrow: escrow.id })
This is the whole trick. The payer runs the acceptance check against the worker's claimed result, and signs a milestone receipt only on a pass. No pass, no signature, no payment — and the receipt embeds a hash of the evidence the check saw, so the tranche is auditable later.
async function accept(m) { const result = await runCheck(m.id) // mechanical, payer-side if (!result.pass) return // no receipt without proof const receipt = channel.sign({ action: "milestone/" + m.id, amount: m.tranche, // the whole tranche at once evidence: result.hash, // what the check saw nonce: channel.next() }) counterparty.send(receipt) }
Milestone receipts flow through the same machinery as per-call receipts: the worker verifies, tallies, and redeems a RAV against the escrow when the balance justifies a transaction. With tranches this large, most workers set the threshold to a single milestone and settle each one as it lands.
import { Receiver } from "@velapay/sdk" const worker = new Receiver({ threshold: "$25" }) worker.on("receipt", async (r) => { if (!worker.verify(r)) return worker.reject(r) worker.tally(r) // $40 · over threshold await worker.settle(worker.buildRAV()) // draw from escrow })
Suppose the worker lands two of three milestones and then goes dark. Nobody needs a dispute process: the worker holds signed receipts for $75 and redeems them against the escrow; the remaining $25 was never signed for, so once the thawing period elapses it returns to the payer. Both sides walk away with exactly what the receipts support.
const state = await escrow.status() console.log(state.drawn) // $75 · backed by milestone receipts console.log(state.thawing) // $25 · no receipt, returns in 72h await escrow.reclaim() // after thaw · undrawn → payer
Reach for milestones when the task is long, the output is what's being bought, and call volume says nothing about progress — building an index, producing a report, migrating a dataset. Stay with per-call billing when each call is the product, as with a serving API or a priced tool server. The honest cost of milestones is writing acceptance checks: if a mechanical check is harder to build than the work is worth, per-call pricing with a tight trust threshold is the better trade.
The quickstart covers channels and receipts end to end, and the API reference documents escrow, thawing, and the RAV schema.