Skip to content
MCP Five

The LedgerProject #540 / 51

The gate that cannot pause

You cannot make somebody else's server wait for your human. Synchronous refusal, asynchronous approval.

This is the part that took longest to get right, and the answer is a genuine limitation rather than a clever trick. It is worth stating plainly instead of hiding, because pretending otherwise produces a demo that hangs.

Why project #3's answer doesn't transfer

Project #3's gate pauses, and the mechanism is lovely. The loop stops. The HTTP request ends. The entire agent — its whole messages array — is written to Postgres. You close your laptop. A brand-new request, possibly on a different machine, thaws it out five minutes later and carries on.

That works because the thing being frozen is yours. Your agent does not mind waiting. It isn't waiting; it doesn't exist. It is a row.

A sampling request is not yours.

You cannot write somebody else's server to Postgres. You do not have it. All you have is an open HTTP request that is going to expire whether or not your human has looked at their screen.

Synchronous refusal, asynchronous approval

So the gate is shaped differently on purpose:

the refusalimmediate, and free
the human's answera card, minutes later, granting a one-shot allowance
when it takes effectthe agent's next attempt
D10 · the spend gate decision flow
Under the ceiling, it runs and lands on the ledger. Over it, the host refuses synchronously for $0.00 — and writes a ledger row anyway. The human's grant is spent by a later attempt, not by the request that was refused.

Note where the grant is checked: after the ceiling, not before. A grant is only ever spent on a call that actually needed it, so it can't be quietly consumed by the next cheap request that happens along.

The refusal costs nothing, and that is the whole argument

npm run sampling -- --deep
🛑 The Kitchen → summarise_week  [claude-sonnet-5]  6.0¢ estimated — REFUSED, $0.00 spent

tokens on the tab   : 0
refused (not spent) : 6.0¢  ← what the ceiling saved you

Your own server asked your own host to spend your money, and your host said no, for free.

The wording is doing real work

This is project #3's deny-branch lesson, one network hop further out — and it cost real money to learn the first time.

Say only "denied" and the server's own model very reasonably retries the identical call. Which gets denied again. And each retry costs the agent another full round trip, because that is how multi-round-trip works. A bad sentence is a bill.

So the refusal text names the price, names the ceiling, and says what would actually help:

REFUSED BY THE HOST'S SPEND GATE. This request is estimated at 6.0¢, which is over the 2.0¢ ceiling configured for the server "kitchen". No model call was made and nothing was charged. Do not retry this request unchanged — either ask for a smaller maxTokens, send less text, or proceed without the model's help and say plainly what you could not do.

Three things in there, each load-bearing: what it cost, what the limit was, and three specific alternatives. A refusal that only refuses turns into a loop.

The demo, in three commands

bash
npm run ledger -- --ceiling kitchen 2     # two cents per request
npm run sampling -- --deep                # 🛑 your host tells your own server no
npm run ledger -- --grant kitchen 8       # a human says "yes, this once"
npm run sampling -- --deep                # …and now it goes through

Checkpoint

The third command writes a grant. The fourth spends it — and the ledger ends up with a refused row and an allowed row for the same request, which is exactly the history you want when somebody asks later why that 6¢ was spent.