A buyer that is software

The running jobjob_7Hq2 · delivered · document.fr-CA.pdf · $49.00 charged

services.dev/agent buys from any brand by its card. It asks the price, orders within a budget, waits through any person's line, and accepts.

services.dev/agent is a buyer in a few lines of code. It needs no generated client. It reads a brand's /.well-known/agents.json, connects to the MCP server the card names, and calls the same tools any agent would.

TypeScript
import { connect } from 'services.dev/agent'
import translation from './services/translation.ts'

const lingua = connect('http://localhost:8787', { budget: 100 })
const lease = { url: 'https://example.com/lease.pdf', name: 'lease.pdf' }

const job = await lingua.order(translation, { document: lease, to: 'fr-CA' })
if (!('done' in job)) throw new Error(job.type === 'OFFER' ? job.price.sentence : job.reason)

const done = await job.done()
if (done.status !== 'delivered') throw new Error(`${done.id} ${done.status}`)
console.log(`${done.id} · delivered · ${done.output.document.name}`)

await job.accept()

Run it beside npx services.dev:

Terminal
$ node buy.ts
job_7Hq2 · delivered · lease.fr-CA.pdf
job_7Hq2 · accepted · spent $49 of $100

order() sends the input without max_usd, so the first answer is the OFFER. When the price fits what's left of the budget, it sends the offer's next back unchanged, key included, so a retry never orders twice. When it doesn't fit, order() returns the offer for you to show a person.

Output
→ GET /.well-known/agents.json
← 200 · lingua · mcp http://localhost:8787/mcp
→ initialize
← lingua · "Without max_usd you get an OFFER: send its next call unchanged to order."
→ tools/list
← order_translation  "Translation: send a document and a language; get a document back. $49 per job, within 48 hours, charged on delivery. AI delivers every job."
  get_job · accept_job · send_back_job · cancel_job · and 8 more
→ order_translation {document: document.pdf, to: "fr-CA", key: "lease-fr-CA"}
← OFFER · $49 per job, within 48 hours, charged on delivery.
  next: order_translation {document: document.pdf, to: "fr-CA", max_usd: 49, key: "lease-fr-CA"}
→ order_translation {document: document.pdf, to: "fr-CA", max_usd: 49, key: "lease-fr-CA"}
← job_7Hq2 · working · $49.00 held
→ get_job {id: "job_7Hq2", wait: 50}
← job_7Hq2 · delivered · document.fr-CA.pdf · $49.00 charged
  stage: review · payment: charged $49
→ accept_job {id: "job_7Hq2"}
← job_7Hq2 · accepted · document.fr-CA.pdf · $49.00 final
TypeScript
export const METHODS = {
  order: 'service.order',
  get: 'job.get',
  done: 'job.wait',
  accept: 'job.accept',
  sendBack: 'job.send_back',
  cancel: 'job.cancel',
} as const satisfies Record<Exclude<keyof Agent, 'spent'> | Exclude<keyof Bought, 'id' | 'job'>, OpId>

Each function calls its row's MCP tool, so the agent can do exactly what the tools allow. It can't do what only a person does (What an agent can never do).

done() long-polls until the job is delivered, accepted or canceled. A job waiting on a party comes back at once, with the link only that person can use:

Output
const job = await agent.order('engagement', { client: ada, matter: 'a lease review' })
← job_7Hq2 · working · $120.00 held

const now = await job.done()
← job_7Hq2 · waiting · on party · $120.00 held
  next    {"do":"hand_to_person","url":"http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_","to":"sign"}

agent.spent
← 120

Hand the link on. Once they act, done() waits through the rest. Over the buyer key's cap, the offer's next is checkout, a person's step, so the agent returns the offer without spending.

Worked in: the agent's order, one deal from the OFFER to the payment.

Your brand's typed client throws a refusal. The agent returns it: order() answers a bought job, an offer or a refusal, so a loop can tell the three apart and decide what to do next.

TypeScript
export interface AgentOptions {
  /** The most this agent spends in all, in dollars. */
  budget?: number
  /** The buyer key, sent as Authorization: Bearer. On localhost dev takes a call with none. */
  key?: string
  /** How requests go out: a recorder, or a server in the same process. */
  fetch?: (request: Request) => Promise<Response>
}

budget caps what the agent spends across every order, and spent reads it back.