One Economics Boundary for Paid Surfaces
May this run, and who pays? Paid paths that join KDCube's accounting boundary use one lifecycle: verify, reserve, run, and settle.
Your application already knows how to spend money on a user's behalf: an LLM call, an embedding, a web search. The question is not whether it costs — it is whether the platform gets to decide, before the call runs, that this user can afford it, and to charge the real cost afterward. Paid paths that join KDCube's accounting boundary use the same decision, without each application reimplementing plans, wallets, or budgets.
A paid surface is any place an application reaches a paid service: a chat turn, a standalone search box, a REST operation, a background job, a cron routine. The chat entrypoint already runs turns under the economics model. Other accountable work — search, REST operations, jobs, cron, or custom calls — joins the same model by using the enforcement engine. A direct, uninstrumented paid call remains outside this boundary.
Verify, reserve, run, settle. Deny before anything runs — and no money hold is left behind.
Verify that the user's quota and funding can cover an estimate; reserve that estimate so concurrent requests cannot oversubscribe it; run the work; settle the actual cost across the funding sources.
01 One request is one funding split
Before it can reserve anything, the engine resolves who pays. Every request is a single split. A primary funding source — an external subscription's period budget, or the project budget for everyone else — covers the part bounded by both the remaining plan quota and the primary funds. The user's wallet covers the remainder.
R = estimated cost plan_part = min(R, Q, P) # Q = remaining plan quota, P = primary funds wallet_part = R − plan_part # the over-quota / over-funds remainder
When the plan quota or funds run out, plan_part simply shrinks
toward zero and the wallet covers the rest in the same pass. The request only
denies when there is no wallet, or the wallet cannot cover the remainder. At
settlement the same order holds — plan quota and primary funds first, wallet
for the over-quota remainder — and if the actual spend still overshoots, the
project budget absorbs the residual as a last resort. Subscriptions and wallets
never go negative.
This is the part your application does not want to rewrite, and does not have to. It resolves once, in the engine, from the request's economics subject.
02 Two entry points, chosen by who settles
The engine offers exactly two ways in, and the choice is about who settles the cost.
EconomicsGuard is an async context manager around one
accountable flow. On enter it resolves plan and funding, admits against quota,
reserves the estimate, and binds accounting to the flow. On exit it reads back
the flow's usage and settles the actual cost — committing or releasing the
reservation.
async with EconomicsGuard( self, subject=subject, scope_id="report_render_42", flow="reports.render", estimate=EconomicsEstimate(reservation_usd=0.05), ) as decision: result = await do_the_paid_work() # accounted calls run here
economic_preflight is the same admit and funding resolution
with no reservation and no settlement — a feasibility gate for
when the cost is metered elsewhere, or the caller degrades gracefully on
denial.
try: await economic_preflight(self, subject=subject, estimate=EconomicsEstimate(reservation_usd=0.01), flow="reports.preview") except EconomicsLimitException: use_cheaper_path()
Both raise EconomicsLimitException before any work runs. The
code says why — rate_limited,
no_funding_source, quota_lock_timeout — and the
surface picks its reaction.
03 Search degrades instead of failing
The most common paid surface is semantic search, and it has a purpose-built facade so components never touch the guard. An economics-enabled entrypoint hands a searchable component one dependency:
model_service = self.search_model_service(flow="reports.search", subject=subject)
Two methods, deliberately different in how they fail.
embed_texts([...]) embeds document/index material and lets
exceptions propagate — a write must not silently skip content.
embed_search_query(query, flow=...) wraps the query embed in a
guard and returns None on economics denial, so
the query layer falls back to lexical ranking (BM25, keyword)
instead of failing the request. Memory search, canvas pin search, and task
search all degrade this way.
Search under economics does not break — it gets cheaper.
04 The same work is never charged twice
A chat turn marks itself as the active parent economics scope while it runs the bundle core. A guard — or the search facade — entered inside that scope automatically degrades to verify-only: it checks feasibility but leaves the usage event in the parent turn, and the turn settles it. A guard outside any parent scope creates and settles its own operation scope. You do not wire this; a context variable carries it.
One unit of work settles once.
05 What your application actually writes
Becoming economics-enabled is extending one base
(BaseEntrypointWithEconomics), which binds the runtime primitives
the engine reuses. From there, guarding a paid surface is three decisions:
resolve the subject (who pays), size the estimate (a few cents for most
non-chat flows), pick EconomicsGuard or
economic_preflight. The plans, wallets, quota windows,
reservations, shortfall absorption, and settlement math stay in the engine.
Chat turns take their reservation floor from one app property:
{"economics": {"reservation": {"chat": 0.4}}}
A scalar USD amount; unset inherits the platform default from the economics
descriptor's reservation.chat, and a value ≤ 0 disables the floor
in favor of the token-based estimate.
And when the paid work is your own — not an LLM, embedding, or search, but a
metered API you call — you make it countable with a tracker so the
guard has a real cost to settle. That is a short, separate move, covered in the
companion journal note and recipe. The alternate runtimes ride the same rails:
a Claude Code turn is accounted as ordinary LLM usage — same tracker,
provider="anthropic", a self-reported per-turn cost — priced right
beside the built-in ReAct runtime's calls.
The result is a single answer to "may this run, and who pays?" that holds whether the paid call sits in a chat turn, a search box, a REST handler, or a job — and an application that spends a user's money only when the platform has agreed it can.