About guardrails
Guardrails put governance around AI usage. They set a hard ceiling on what a user, an agent, or an app can spend, and on how often it can run. UC AI enforces this ceiling around every agent run.
Guardrails are part of the paid UC AI Pro layer. They install next to core UC AI and start automatically. Your agents and your generate_text calls need no change.
The two features
Section titled “The two features”| Feature | Caps | Answers |
|---|---|---|
| Budget | Monetary cost (priced from token usage) | “This app can spend at most $100 this month” |
| Rate limiting | Counts — runs, tool calls, concurrency | ”This user can start at most 100 runs per hour” |
Both features share the same scope and audience vocabulary. Either one can express “anonymous visitors get a little, logged-in users get more”. See the recipe.
Use them together. Budgets stop runaway cost. Rate limits stop runaway frequency: abuse, retry storms, and infinite tool loops.
How it plugs into core
Section titled “How it plugs into core”Core UC AI gives a generic execution hook: a package named UC_AI_HOOK. If this package is present and valid, UC AI calls it around every top-level execute_agent run, and before every tool call. In Pro, UC_AI_HOOK is a dispatcher. It sends each callback to every enabled feature in uc_ai_pro_hook_subscribers.
uc_ai_agents_api.execute_agent │ before_execution / after_execution ▼ UC_AI_HOOK (Pro dispatcher) ├──► UC_AI_RATELIMIT_HOOK (sort_order 50) └──► UC_AI_BUDGET_HOOK (sort_order 100)
uc_ai_tools_api (inside the tool-calling loop) │ before_tool_call ▼ UC_AI_HOOK ──► subscribers flagged tool_hook = 'Y' (rate limiting)Each feature reads the token counts, the identity, and the timestamps directly from the public uc_ai_agent_executions table. The Pro layer keeps only its own configuration, and a cost ledger for budgets. At the start of a run, core writes the class of the caller to uc_ai_agent_executions.audience as public, authenticated, or db. This column lets the guardrails separate anonymous traffic from signed-in traffic.
before_executionruns before a run starts. A guardrail raises an error here to veto the run. This is a hard cap, and the run spends no tokens.before_tool_callruns before each tool call. When a run reaches a tool-call cap, rate limiting raises an error here and stops the run in mid-flight.after_executionruns when a run finishes. Budgets use it to price the finished execution tree into the cost ledger. This step is best-effort. It never turns a completed run into a failure.
Shared concepts
Section titled “Shared concepts”Both features share the same governance vocabulary.
Every limit targets a scope — what it counts, and in which bucket:
scope_type | scope_value | Counts |
|---|---|---|
global | (null) | Every execution in the schema |
user | the created_by value | Runs by one named user (APEX user, else DB user) |
agent | an agent code | Runs of one agent |
app | an APEX application id | Runs from one APEX app |
apex_session | (must be null) | Runs in the APEX session of the calling visitor. One bucket for each browser session |
each_user | (must be null) | Runs by the calling user. One row gives each user a separate bucket |
The first four are static. You name the bucket in scope_value. The last two
are per-caller. One configuration row then creates a separate bucket for each
visitor or user. For these two, scope_value must be null. The database rejects
any other value.
UC AI evaluates every limit that matches a run. The strictest outcome wins.
Audience
Section titled “Audience”Every limit also targets an audience — which callers it governs:
audience | Governs |
|---|---|
any (default) | Every caller |
public | An anonymous APEX visitor (a session that is not authenticated) |
authenticated | A logged-in APEX user |
db | A database session (SQL client, scheduler job, or trigger), or the internal session of uc_ai |
audience selects which callers a limit governs. For every value except
any, it also restricts what the limit counts to that same class of caller. A
public limit therefore counts only anonymous usage. Signed-in traffic can never
consume an anonymous allowance, and anonymous traffic can never consume a
signed-in allowance.
For each run, UC AI reads the class of the caller from the live APEX session. It
asks APEX whether the session is authenticated. A username cannot answer this
question, because every anonymous visitor of an application shares one APEX
public user name, nobody by default. With scope_type = 'user', all these
visitors share one bucket.
Enforcement mode
Section titled “Enforcement mode”enforcement_mode | Behavior when the limit is reached |
|---|---|
hard | Deny — raise an exception and abort the run / tool call |
soft | Warn — log a warning, allow the run to proceed |
advisory | Track only — never warn or deny |
warn_threshold_pct (default 80) sets when a limit starts to warn. At the default, the limit warns at 80% of the cap and denies at 100%.
Recipe: public visitors a little, logged-in users more
Section titled “Recipe: public visitors a little, logged-in users more”The standard setup for an AI feature on a page that anonymous visitors can reach takes three rows:
-- 1. Each anonymous visitor: 3 runs per browser session per dayinsert into uc_ai_rate_limits ( code, description, audience, scope_type, limit_type, limit_count, window_kind, period, enforcement_mode) values ( 'PUBLIC_SESSION_DAY', 'Anonymous: 3 runs/session/day', 'public', 'apex_session', 'AGENT_EXEC', 3, 'calendar', 'day', 'hard');
-- 2. Backstop, NOT optional: a visitor can clear cookies for a fresh session and-- a fresh quota, so also cap anonymous traffic in aggregate.insert into uc_ai_rate_limits ( code, description, audience, scope_type, scope_value, limit_type, limit_count, window_kind, period, enforcement_mode) values ( 'PUBLIC_APP_DAY', 'Anonymous: 500 runs/day across the app', 'public', 'app', '512', 'AGENT_EXEC', 500, 'calendar', 'day', 'hard');
-- 3. Logged-in users: 100 runs EACH per day — one row covers everybodyinsert into uc_ai_rate_limits ( code, description, audience, scope_type, limit_type, limit_count, window_kind, period, enforcement_mode) values ( 'AUTH_USER_DAY', 'Logged in: 100 runs/user/day', 'authenticated', 'each_user', 'AGENT_EXEC', 100, 'calendar', 'day', 'hard');commit;Row 2 has audience = 'public', so its 500 counts only anonymous runs. Your
employees in the same app cannot exhaust the allowance of the visitors. The
visitors cannot exhaust the allowance of the company.
The same three shapes work for budgets
with limit_amount in place of limit_count. Examples are $0.10 for each
anonymous session per day, and $5 for each logged-in user per day.
What to watch out for
Section titled “What to watch out for”- Do not use
each_userfor anonymous traffic. All anonymous visitors share onecreated_by. A per-user bucket therefore becomes a shared cap, and the first visitor of the day locks out everybody else. The database rejectseach_usertogether withaudience = 'public'. Useapex_sessioninstead. apex_sessioncannot useperiod = 'total'. APEX recycles its session ids. An all-time bucket therefore gives a new visitor an exhausted quota, and the cause is hard to find. The database rejects this combination also.- Database and job callers resolve to
db. Anaudience = 'any'limit governs them. Aneach_userlimit governs them also, in a bucket for their database username. Anapex_sessionlimit never applies to them. - UC AI evaluates the audience again before every tool call. A tool that opens its own APEX session can therefore change the tier inside a run.
- The check-then-act race still applies. Two runs that start at the same
moment can both pass, because neither row exists yet. A hard cap of N can
therefore allow N+1 for a short time. Small per-visitor caps show this effect
more than coarse limits. If simultaneous requests matter, add a
CONCURRENCYlimit.
Install
Section titled “Install”Core UC AI must already be installed in the schema. Then run the Pro installer:
@ext/uc-ai-pro/install_uc_ai_pro.sqlThe script installs the UC_AI_HOOK dispatcher and the subscriber registry. It then installs each feature: budget and rate limiting. A new database session activates the hook automatically. No per-session registration is necessary.