Context Fallbacks
Context fallbacks let you reuse policies across related execution modes without duplicating rules. When no policy matches a given mode, the engine retries evaluation with a fallback mode.
The problem
Consider a system with several execution modes: interactive, background, scheduler, bot_processor, realtime. Many of these non-interactive modes should follow the same policies as background, but writing separate rules for each is tedious and error-prone:
# Without fallbacks -- repetitive and fragile
policies:
- id: deny-bg-high
condition:
modes: [background]
risk: [high]
effect: deny
- id: deny-scheduler-high # Same logic, different mode
condition:
modes: [scheduler]
risk: [high]
effect: deny
- id: deny-bot-high # Same logic again
condition:
modes: [bot_processor]
risk: [high]
effect: deny
The solution
Declare a context_fallbacks map at the top level. The engine uses it to walk a fallback chain when no policy matches directly:
context_fallbacks:
scheduler: background
bot_processor: background
realtime: background
policies:
- id: deny-bg-high
condition:
modes: [background]
risk: [high]
effect: deny
Now a tool invoked in scheduler mode with high risk will:
- Try to match policies with
mode=scheduler. No match. - Look up
schedulerincontext_fallbacks– findsbackground. - Retry evaluation with
mode=background. Matchesdeny-bg-high. - Return
effect: deny.
One rule covers background, scheduler, bot_processor, and realtime.
Multi-level chains
Fallbacks can chain multiple levels deep:
context_fallbacks:
cron: scheduler
scheduler: background
A tool in cron mode will try: cron -> scheduler -> background.
Cycle detection
The engine detects cycles and stops. If your fallback map contains a -> b -> a, the engine will try a, then b, see that a was already visited, and stop. It then falls through to defaults.
# Safe -- the engine handles this gracefully
context_fallbacks:
a: b
b: a
How it interacts with evaluation
The full evaluation flow:
- Sort policies by priority.
- Find the first enabled policy that matches the original context. If found, return its verdict.
- If no match, check
context_fallbacksfor the current mode. - If a fallback exists, replace the mode in the context and re-evaluate (step 2).
- Repeat until a match is found, the chain is exhausted, or a cycle is detected.
- If still no match, return the defaults.
Only the mode field changes during fallback. All other context fields (tool, model, risk, user, etc.) remain the same.
Accessing fallbacks programmatically
All three SDKs expose the loaded fallback map:
Python
engine = PolicyEngine(ps)
print(engine.context_fallbacks)
# {"scheduler": "background", "bot_processor": "background"}
TypeScript
const engine = new PolicyEngine(ps);
console.log(engine.contextFallbacks);
// { scheduler: "background", bot_processor: "background" }
Go
engine := guard.NewPolicyEngine(ps)
fmt.Println(engine.ContextFallbacks())
// map[scheduler:background bot_processor:background]
Common patterns
Non-interactive modes fall back to background
context_fallbacks:
scheduler: background
bot_processor: background
realtime: background
Environment-specific chains
context_fallbacks:
staging: production
preview: staging
A tool in preview mode inherits staging policies, which in turn inherit production policies.