mcpbelt

You queued the wrong file, and the money has not left yet

While a job is still waiting its turn, cancelling it gives the credit back. Once an engine has started, it cannot, and the tool says so.

The problem

01

The agent queued an hour of audio and it was the wrong hour of audio. Or it was the right one, and the person paying changed their mind thirty seconds later. Either way the job is sitting in a queue, nothing has happened to it yet, and money is about to be spent on a result nobody wants.

This is a normal amount of wrong. An agent working from an instruction picks a file from a list, and lists have neighbours. The mistake is cheap to make and, in most setups, impossible to take back.

What you want in that moment is narrow and specific: stop the job that has not started, get the credit back, and be told plainly if you were too late. Not a best effort, not a maybe.

Why it fails locally

02

With a provider API there is usually nothing to cancel, because there was never a queue. You made a call, the call is running, and the charge was incurred the moment the request was accepted. There is no gap to cancel inside.

What is often called cancelling is closing the connection, and that only stops you listening. The work continues on the other side and is billed in full: you have thrown away the result you paid for, which is worse than not cancelling. On the long-job APIs that do have a cancel endpoint, the honest reading is the same, because the meter starts when processing starts and cancelling after that recovers nothing.

Building the queue yourself moves the problem rather than solving it. Now you hold jobs in your own queue and you have to decide, at the exact instant a request to cancel arrives, whether a worker has already picked the job up. Get that race wrong in one direction and you refund a job that ran; get it wrong in the other and you charge for one that never did.

The code

03

cancel is free and takes the handle a queued job returned, the same one you would pass to result.

listen(key="<key>")
  -> handle, status: running, estimated_cost_eur, estimated_minutes

cancel(handle="<handle>")
  -> cancelled: true, refunded_eur

# once an engine has started, the same call answers honestly:
#   -> cancelled: false, refunded_eur: 0, reason
#   and the job runs to the end, so you still collect it:

result(handle="<handle>")
  -> the transcript, as usual

What it costs

04

cancel costs nothing. It is one of the free calls, along with upload, result, account, and calling it on a job you turn out to be too late for is free as well.

A cancelled job returns the credit that was set aside for it. refunded_eur is the amount that came back, and it is a real release of reserved credit, not a coupon and not a gesture.

That reservation is also why the boundary is where it is. Credit is committed before the work starts and settled when it finishes, and the charge never exceeds what was committed. While the job waits, the commitment can be undone. After an engine has read the file, the money is out, and no answer we could give would put it back.

What this does not do

05
  • It works only while the job is still waiting. cancelled comes back false when it is too late, and reason says why: that is the whole contract, and it is not softened anywhere.
  • Work that ran inline never had a queue to sit in. There is nothing to cancel, only a result you already have.
  • Cancelling is not deleting. It stops a job that has not started; it does not remove a finished result, and results expire on their own.
  • Calling it twice on the same handle is safe and changes nothing. The second call is not an error and does not refund twice.

Without markup

This page in markdown: /problems/cancel-a-job-that-has-not-started.md. All 20 of them in one file: /llms-full.txt.

Nearby problems