The decision engine evaluates guardrail rules at two boundaries: run start and step creation. Evaluation short-circuits on the first DENY — if the kill switch is active, no other rules are checked.
Evaluation Priority
Rules are evaluated in this exact order:
| Priority | Rule | Deny Reason | Run Start | Step |
|---|
| 1 | Kill switch | KILL_SWITCH_ACTIVE | Yes | Yes |
| 2 | User blocked | USER_BLOCKED | Yes | Yes |
| 3 | Workspace daily budget | WORKSPACE_DAILY_BUDGET_EXCEEDED | Yes | Yes |
| 4 | User daily budget | USER_DAILY_BUDGET_EXCEEDED | Yes | Yes |
| 5 | Monthly run limit | MONTHLY_RUN_LIMIT_EXCEEDED | Yes | No |
| 6 | Max concurrent runs | MAX_CONCURRENT_RUNS_EXCEEDED | Yes | No |
Rules 5 and 6 only apply at run start — they don’t make sense for individual steps within an already-running run.
Run Start vs Step Checks
Run Start (all 6 rules)
When guard.run() is entered, the SDK calls POST /v1/runs/. The engine evaluates all 6 rules:
- Is the kill switch active?
- Is this user blocked?
- Has the workspace exceeded its daily budget?
- Has this user exceeded their daily budget?
- Has the workspace hit its monthly run limit?
- Are there too many concurrent runs?
If allowed: a new run is created with status RUNNING, the concurrent run counter is incremented, and the monthly run counter is incremented.
Step Checks (rules 1-4)
When run.model_call() or run.tool_call() is called, the SDK calls POST /v1/runs/{id}/steps. The engine evaluates rules 1-4:
- Is the kill switch active?
- Is this user blocked?
- Has the workspace exceeded its daily budget?
- Has this user exceeded their daily budget?
This catches cases where budgets are exceeded mid-run, or where the kill switch is activated while a run is in progress.
Two-Tier Evaluation
Pikarc uses a two-tier evaluation strategy for performance:
Request → Try Redis cache → cache hit? → evaluate from cache → return
→ cache miss? → warm from Postgres → retry
→ still miss? → fall back to full DB query
Fast Path (Redis)
All guardrail data is cached in Redis:
- Kill switch state
- Workspace config (budgets, concurrent limit, plan, monthly run limit)
- User blocked status
- Daily spend counters (workspace and per-user)
- Concurrent run count
- Monthly run count
Spend counters use microdollars (1 USD = 1,000,000 microdollars) for atomic INCRBY without floating-point drift.
Fallback Path (Postgres)
If any Redis value is missing, the engine attempts to warm the cache from Postgres and retry. If the cache is still incomplete, it falls back to a full database evaluation.
In practice, the Redis fast path handles the vast majority of requests. The Postgres fallback is a safety net for cold starts and Redis failures.
Decision Record
Every evaluation produces a Decision record stored alongside the step:
{
"outcome": "DENY",
"reason": "WORKSPACE_DAILY_BUDGET_EXCEEDED",
"evaluated_rules": {
"kill_switch": "PASS",
"user_blocked": "PASS",
"workspace_daily_budget": "DENY"
}
}
The evaluated_rules field shows exactly which rules were checked and their results, which is useful for debugging why a request was denied.
Kill Switch
The kill switch is the highest-priority guardrail. When active, every run start and step creation is immediately denied with KILL_SWITCH_ACTIVE. No other rules are evaluated.
Toggle it via the API:
# Enable
curl -X POST http://localhost:8000/v1/workspace/kill-switch \
-H "Authorization: Bearer lg_..." \
-H "Content-Type: application/json" \
-d '{"active": true}'
# Disable
curl -X POST http://localhost:8000/v1/workspace/kill-switch \
-H "Authorization: Bearer lg_..." \
-H "Content-Type: application/json" \
-d '{"active": false}'
Or use the toggle in the Configuration page of the dashboard.
The kill switch requires the Scale plan or higher.