message is one user turn; a generation is one app
the agent built. Both are things a person does — never tokens, never calls — so
you write the policy in the units you sell in.
Cap one user
app/api/vendo/[...vendo]/route.ts
count(action, window?) is already bound to the user this request resolved to. A
policy never names a subject, so it can never read another person’s usage by
accident.
Return true and the action runs and is counted. Return false and it is
refused and never counted.
Cap a whole org
Every org your host asserts for a request is already a shared meter, namedorg:<orgId>. There is nothing to wire.
app/api/vendo/[...vendo]/route.ts
pool counts the whole bucket instead of the one person, so five members
spending a thousand messages each reach a five-thousand-message org cap
together.
user.pools lists the pools this caller draws from. One org:<orgId> entry
arrives for every org your memberships seam asserted, which is the only wiring
an org cap needs — see Orgs & memberships.
The pool name is the same string a grant uses for that org —
org:acme is the
sharing principal and the meter. An org is spelled one way everywhere.examples/demo-bank wires exactly this: one memberships
seam, one org cap, and a refusal worded in Maple’s own voice.
Both at once
Per-user and per-org are one callback, and the firstfalse ends it, so the
tighter cap wins.
limits
count is one live read of the meter, which is why it is a callback and
not a number handed to you: most policies read one window, and pre-computing
every window a policy might ask about would be a query per action per call.
Order the cheap branch first and a policy that returns early does less work.
Tiers are a branch on your own data. user.facts is the bag your auth preset
asserted — plan, role, seat count, tenure — so a Pro user and a Free user read
different numbers out of the same policy.
limits
Windows
The three durations are summed into one lookback.since names an instant floor
instead. Omit all four and the count is all-time.
A billing period is the
since case, and the date is yours:
limits
What a blocked user sees
A refused message never reaches the model. The check runs before the thread is even read, so a refused turn costs no read, no write, and no model call — and the surface renders one notice in the thread, where the answer would have been. Return the object form to write that sentence yourself:limits
A reached cap is a status, not a failure, so the notice carries no alert mark.
There is deliberately no
{ allow: true }. Allowing has nothing to say.
A refused generation reads differently on purpose: the turn carries on, because
the agent can talk about a build that did not happen. It is told the facts — that
nothing was built, your sentence when you wrote one, and that calling again gets
the same answer.
The fail-closed rule
A policy that throws denies. A limits system that fails open stops limiting silently: you keep believing you have a cap while every user is unlimited. A turn that was refused and said so is strictly better. Every such denial logslimits.callback_error.
- Counting a
poolthis user is not in throws, and therefore denies. Answering0for a meter that was never resolved would silently under-count every limit written against it. The error names the pools the user does have. - A meter that cannot be read right now still denies, but never dressed as a cap they reached — nothing was counted, so the notice says the check failed and that it is temporary.
- A store with no usage meter is refused at composition, not at request time.
createVendo({ limits })throws naming the gap, because every count would read0and no limit would ever be reached.
Per-tenant caps
If you let Vendo Cloud keep your tenant directory, caps come with it — set in the console, not in code. Each one carries a scope:
The project sets defaults; a tenant can override them. An explicit
config.limits wins over both — the same precedence rule as everywhere else
on this page, just supplied by Cloud instead of your own callback.
What is not metered
Automation firings. A schedule fire or a webhook delivery is nobody’s request and has no per-user meter to spend, so an app built inside one is not gated by your policy. What a person does in front of the product is: the message they send, and the app the agent builds for them. Those are the two chokes, and there is nothing to configure — settinglimits arms both.
Where to go next
Orgs & memberships
The one seam an org cap needs, and the three other things it unlocks.
membershipsYour users
Where
facts comes from, and what a tier branch may read.user.factsHandler options
Every
createVendo key, including this one in one table row.limits