[KongchangAI]
· 3 min read· 1,896 words

AI Agent Spend Gate in Practice: A Deep Review of 35 Rejections in 43 Decisions

AI Agent Spend Gate in Practice: A Deep Review of 35 Rejections in 43 Decisions

An AI agent spend gate should deterministically block before the call and write to a hash-chain ledger, and the denial path is its real value.

The article describes an AI agent spend gate deployed on a local loopback port: all agents must point at the gate, which performs six layers of checks before the request leaves the host—authentication, quota caps, pre-call budget reservation, and fail-closed—writing each decision to an append-only JSONL hash-chain ledger. Of the first 43 decisions, 35 were denied, 33 of them because the token couldn't resolve to an agent identity, showing authentication is the primary interception point. However, an independent audit revealed an enforcement-boundary gap: nftables rules block only one uid, while another channel can reach the provider directly via sudo and docker to read the real keys, so the author refuses to call spend "capped." The core lesson: the denial path is the product, the allow path is only five lines; alerts must reach humans, not just files.

Why a machine that says "no" matters more than a dashboard

When most teams talk about AI agent cost control, they think of dashboards—a reporting tool that tells you after the fact where the money went. But this article describes a fundamentally different approach: run a deterministic check before the provider call happens, deciding "allow" or "deny." Every decision is written to a ledger that cannot be quietly tampered with. It's not "monitor the invoice," it's "block first, then record."

The author's design goal is narrow but sharp: no agent in the company can spend money without passing a deterministic check, and whether allowed or denied, every decision must land in a ledger that cannot be silently edited. That goal itself is worth pondering—it acknowledges the limitation of dashboards: reporting can only tell you what already happened, while a gate can stop what hasn't happened yet.

AI agent spend gate architecture diagram

The gate's technical architecture: how six layers of checks intercept the request before it leaves the host

This gate is deployed on top of a local proxy. Six agents no longer talk to the provider directly—they point at http://127.0.0.1:8899/anthropic. The gate runs as a systemd unit on that loopback port, holds the real keys, and forwards to upstream—the agents themselves never hold the keys.

Before a request leaves the host, the gate checks the following in order:

Authentication. Agents are identified by the sha256 digest of each agent token. The config file carries only digests—the file's own comment states it plainly: plaintext gate tokens must never appear in it. No token, or a token that doesn't map to any agent: deny, and the request never reaches the provider.

Quota caps. 0.50 EUR per request; 300 requests and 4.00 EUR per day; 3,000 requests, 20.00 EUR, and 4,000,000 tokens per month. Configurable per provider—fal image calls estimate at 0.004 EUR, Anthropic at 0.03 EUR.

Pre-call budget reservation. Each request first reserves the worst-case cost, then settles against actual metered cost. The reservation is an atomic "check-and-increment" operation that validates every cap under a lock, so two agents can't pass the check simultaneously and jointly breach the limits.

Fail-closed. If the vault lookup fails or upstream errors out, the reservation is released and the request is denied.

There's a second, cruder mechanism at the network layer: a set of nftables rules drops outbound 80/443 traffic for a dedicated service uid, so "just call the API directly" isn't an option for that account.

What is the "check-reserve-settle" model?

This is a classic pattern in distributed resource quota systems, usually called reserve-then-settle. The core idea: before a request actually consumes resources, it first occupies a slice of quota sized at the worst case (reserve), then once the request completes, the bill is backfilled at actual consumption and the excess reservation is released. This is necessary because the actual cost often can't be known until the request returns—for example, a token-billed inference call where all you know beforehand is the prompt length, not how long the model will generate.

Without reservation, two concurrent requests could each see "only 0.5 EUR left in budget" and then each spend 0.4 EUR, jointly blowing past the cap. The reservation mechanism uses an atomic "check-and-increment" operation to validate every cap under a lock, eliminating this race condition. The article's "reserve 0.05 EUR, settle at 0.004 EUR" is a direct instance of this model: the extra 0.046 EUR is returned to the budget pool after the request finishes. Only by understanding this model can you see why the gate can make an "allow/deny" decision before the call, without waiting for the invoice to arrive.

The real record of the first 43 decisions: 33 were due to token problems

The ledger is append-only JSONL. One record per decision, each carrying a prev field containing the hash of the previous record, plus a separate header file holding the current tail. Change any historical line and the chain fails verification. As of the article's data read, the ledger had 43 records: 35 denials, 8 allows.

The denials are the interesting part. 33 of the 35 denials were bad_or_unscoped_token—the caller arrived with a credential that couldn't resolve to an agent. One each was triggered by an unknown provider and the daily request cap. A few were the author himself fumbling with token formats during configuration. The gate doesn't care that the caller is "obviously" legitimate. It has no agent identity, so nothing leaves the host.

The allows split into two categories, one of them embarrassing:

Two rows were test-harness rows, written into the production ledger by test tooling while exercising the allow path. One of them returned upstream 401—yet still consumed about 0.03 EUR in estimated metered spend, because providers can bill for failed requests. Any invoice reconciliation must manually exclude these rows.

Six rows were real: the builder agent generated images via fal, each call reserving 0.004 EUR, holding 0.05 EUR at reservation time, actual cost 0.004 EUR each.

35 out of 43 decisions were "no." That's the number the author wants in front of anyone who thinks agent spend gating is just a dashboard.

Gate decision log illustration

The difference between a hash-chain ledger and "silently tamperable"

An ordinary log file is mutable: anyone with write access can delete a line, change a number, and afterward nobody can be sure. A hash-chain ledger borrows the idea from blockchain: each record stores a prev field containing the hash of the previous record. That way, if any historical record is modified, every record after it has a mismatched hash, and verification fails immediately. A separate header file holds the current chain tail, preventing anyone from quietly appending a forged record.

The key point: a hash chain doesn't prevent modification, it only makes modification detectable. For an internal spend gate, that's exactly sufficient—the attacker could be a member of your own team, and you're not defending against external hackers; you want "I edited the ledger" to be impossible to hide in silence. It turns "we recorded every decision" into "our log either verifies or it doesn't," at very low cost with a high trust payoff. The prev field on each of the 43 decisions in the article is a concrete instance of this chain.

The honest gaps: why the author refuses to call spending "capped"

This system was built by the team and then attacked by an independent auditor. The auditor's findings are the most valuable part:

Alerts are file-only. Denials are written to alerts.jsonl, and the gate's notification command is empty. When a denial happens, nobody is told. "Alert triggered" would overstate the actual state—it's just a growing file.

Denial-log omission. The denials.jsonl configured at audit time was 0 bytes, while alerts.jsonl carried the denial records, so an operator reading the file that claims to record denials would conclude "no denials." More critically, no record carries the limit value: you can see that some cap fired, but not how close you were to the line.

Rates are placeholders. The config file says it itself: until rates match the provider's current price sheet, cost tracking is only an estimate.

The biggest problem: the enforcement boundary isn't where the author said it was. The nft rules only block one uid; the channel that spawns the generation worker runs under a different uid, with passwordless sudo and docker group membership, so it can reach the provider host directly and read the real keys. The audit's conclusion is blunt: this slice is defense in depth, not enforcement. The author is fixing the host architecture; until then, he won't describe his provider spend as "capped" in any article.

That last point is the entire reason the author decided to publish this article. A gate that can be bypassed still has value—it makes the default path auditable, and it caught 33 malformed requests including the author's own—but calling it a cage would be a lie, the kind of lie that reads like a capability until the invoice arrives.

If rebuilding, what to do first

The denial path is the product. The allow path is five lines of code. The value is in the atomic "reserve-check-deny," the fail-closed release, and the fact that a denied request never opens a socket.

Hash-chain the ledger on day one. Cheap to add, and it converts "we record everything" into "the log either verifies or it doesn't."

Keep test rows out of the production ledger. Even one row with agent: "probe" in a file you'll reconcile against invoices is one too many.

Alerts are not optional, and not a file. A denial that reaches nobody is just a well-intentioned log entry.

Check the uid before claiming enforcement. This is the mistake the author most wishes others would skip.

Live record as of writing: 43 decisions, 35 denials, 0.024 EUR in total metered external spend for the whole company, and one well-documented hole in the fence.

This article was written with AI assistance—the agent it describes drafted it, and every number was verified against a fixed source.

Background addendum

nftables and where the "enforcement boundary" actually sits

nftables is the Linux kernel's packet filtering framework, built on Netfilter, replacing the old iptables. Its rules take effect inside the kernel network stack, dropping or allowing packets based on uid, port, protocol, and other conditions, and it's harder to bypass than a userspace firewall—even if a process wants to connect directly to an external API, as long as its uid hits a "deny outbound 80/443" rule, the packet is dropped before it leaves the NIC.

But its boundary is drawn by uid, not by "this person" or "this container." That's exactly where the article's audit gap lies: nft only blocks the single uid running the gate, while the channel that actually spawns generation workers runs under a different uid, which isn't covered by the rules and also has passwordless sudo and docker group membership. On most Linux configurations, docker group membership is equivalent to root—you can launch a container that mounts the host filesystem and read the real keys directly. So the nft rules are a form of defense in depth, not enforcement. The former layers obstacles to raise the cost of bypassing; the latter is physically impossible to bypass. The author refuses to say "spend is capped" because presenting the former as the latter would mislead readers.

One-line Summary

An AI agent spend gate should deterministically block before the call and write to a hash-chain ledger, and the denial path is its real value.

Paragraph Summary

The article describes an AI agent spend gate deployed on a local loopback port: all agents must point at the gate, which performs six layers of checks before the request leaves the host—authentication, quota caps, pre-call budget reservation, and fail-closed—writing each decision to an append-only JSONL hash-chain ledger. Of the first 43 decisions, 35 were denied, 33 of them because the token couldn't resolve to an agent identity, showing authentication is the primary interception point. However, an independent audit revealed an enforcement-boundary gap: nftables rules block only one uid, while another channel can reach the provider directly via sudo and docker to read the real keys, so the author refuses to call spend "capped." The core lesson: the denial path is the product, the allow path is only five lines; alerts must reach humans, not just files.

#AI agent spend gate#AI agent cost control#hash chain ledger#nftables enforcement boundary#pre-call budget reservation
Share:

Related articles