Quick Start
Install Savants, add your first guard, and see it block an unauthorized action. No API keys. No cloud required. Single binary, zero dependencies.
Savants Guard works in two places:
- Your IDE — blocks Claude Code / Cursor / Windsurf from running dangerous commands on your machine.
curl -fsSL savants.sh | sh - Your code — blocks your AI agents from doing dangerous things in production.
pip install savants-guardornpm install @savants.dev/guard
Same DSL. Same rules. Two deployment points. Pick one or use both.
Option A: Guard your IDE (Claude Code / Cursor)
Stop Claude from running rm -rf, force pushing, or touching your .env files. One command, 30 seconds:
# macOS / Linux
curl -fsSL savants.sh | sh && savants guard preset standard
# Windows (PowerShell)
irm https://releases.savants.dev/latest/install.ps1 | iex
savants guard preset standard That's it. Claude Code now has guardrails. Use --dangerously-skip-permissions with confidence.
# Choose your safety level:
savants guard preset minimal # 10 rules — catastrophic only
savants guard preset standard # 25 rules — recommended
savants guard preset standard+secrets # + credential protection
savants guard preset standard+k8s-secrets # + k8s secret value protection
savants guard profiles # see all available profiles Option B: Guard your AI agent code
Add guardrails to your own AI agents. Works with any LLM framework. Choose Python or TypeScript.
100% local, 100% free. Runs entirely in your process. No network calls. No Savants account needed. No limits on rules or evaluations. Cloud features (team dashboard, live rule updates) are optional and separate.
Install
pip install savants-guard npm install @savants.dev/guard Add your first guardrail
Python
from savants_guard import create_guard
guard = create_guard([
"when action contains 'delete' and env eq 'production' then block",
"when spend gt 100 then require_approval",
]) TypeScript
import { createGuard } from '@savants.dev/guard';
const guard = createGuard([
"when action contains 'delete' and env eq 'production' then block",
"when spend gt 100 then require_approval",
]); That's it. Two rules, plain English. No YAML, no JSON config files.
Check an action
Now test it. Your AI agent wants to delete a production database:
Python
result = guard.check({"action": "delete_database", "env": "production"})
print(result.blocked) # True
print(result.rule) # "when action contains 'delete'..."
print(result.action) # "block" TypeScript
const result = guard.check({
action: 'delete_database',
env: 'production',
});
console.log(result.blocked); // true
console.log(result.rule); // "when action contains 'delete'..."
console.log(result.action); // "block" Blocked. The action contains "delete" and env is "production." The database is safe.
Integrate with your framework
guard.check() works with any framework. Choose your setup:
Vercel AI SDK
Use guard.wrapTools() to automatically guard every tool call:
import { generateText, tool } from 'ai';
import { z } from 'zod';
const tools = guard.wrapTools({
deleteUser: tool({
description: 'Delete a user',
parameters: z.object({ userId: z.string(), env: z.string() }),
execute: async ({ userId }) => db.users.delete(userId),
}),
});
const { text } = await generateText({ model, tools, prompt });
// Guard blocks before execute() runs. LLM gets a GuardError. Anthropic SDK — Python
import anthropic
from savants_guard import create_guard
guard = create_guard(["when action contains 'delete' then block"])
client = anthropic.Anthropic()
response = client.messages.create(model="claude-sonnet-4-20250514", ...)
for block in response.content:
if block.type == "tool_use":
result = guard.check({"action": block.name, **block.input})
if result.blocked:
print(f"Blocked: {result.rule}")
continue
execute_tool(block.name, block.input) Anthropic SDK — TypeScript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const response = await client.messages.create({ /* ... */ });
for (const block of response.content) {
if (block.type === 'tool_use') {
const result = guard.check({ action: block.name, ...block.input });
if (result.blocked) {
console.log('Blocked:', result.rule);
continue;
}
await executeTool(block.name, block.input);
}
} Any framework (raw check)
guard.check() takes any object and returns a result. Use it anywhere:
// Works with OpenAI, Gemini, LangChain, CrewAI, or custom agents
const result = guard.check({
action: toolName,
env: process.env.NODE_ENV,
...toolArgs,
});
if (result.blocked) {
throw new Error(`Action blocked: ${result.rule}`);
}
// Safe to proceed The GuardResult object
guard.check() always returns a GuardResult with these fields:
| Field | Type | Description |
|---|---|---|
blocked | bool | True for block and require_approval actions. False for everything else. |
allowed | bool | True only when no rule matched at all. False when any rule matched (even soft actions). |
action | str | None | The action from the matched rule: "block", "suggest", "rewrite", "ask", "require_approval", or None |
suggestion | str | None | The message from suggest, replacement from rewrite, or reason from ask. None if no message or no match. |
rule | str | None | The full DSL rule that matched, or None. |
context | dict | The context object you passed to check(). |
Three possible states
# 1. Hard block (block, require_approval)
result.blocked == True, result.allowed == False
# 2. Soft action (suggest, rewrite, ask) — rule matched but not a hard block
result.blocked == False, result.allowed == False
result.action # "suggest", "rewrite", or "ask"
result.suggestion # the message / replacement / reason
# 3. Allowed — no rule matched
result.blocked == False, result.allowed == True Action types
Rules end with an action. Four actions are available, from soft to hard:
| Action | DSL syntax | Behavior | result.blocked |
|---|---|---|---|
suggest | then suggest 'Use chmod 755' | Denies action, provides alternative. Agent auto-recovers. | False |
rewrite | then rewrite 'git push --force-with-lease' | Silently replaces the command. Agent never sees original. | False |
ask | then ask 'Deploy requires approval' | Escalates to user for approval before proceeding. | False |
block | then block | Hard stop. Action is prevented entirely. | True |
guard = create_guard([
"when command contains 'chmod 777' then suggest 'Use chmod 755 for dirs'",
"when command contains 'git push --force' then rewrite 'git push --force-with-lease'",
"when command contains 'npm publish' then ask 'Publishing is permanent'",
"when command contains 'rm -rf /' then block",
])
result = guard.check({"command": "chmod 777 /var"})
print(result.action) # "suggest"
print(result.suggestion) # "Use chmod 755 for dirs"
print(result.blocked) # False — agent can try the alternative Rule evaluation order
First match wins. Rules are evaluated in the order you define them. The first rule that matches determines the result. Put more specific or softer rules before broader or harder ones.
guard = create_guard([
"when action eq 'deploy' then suggest 'Use staging first'", # matches first
"when action eq 'deploy' then block", # never reached
])
result = guard.check({"action": "deploy"})
print(result.action) # "suggest" — first rule won Add rules at runtime
Add or inspect rules without recreating the guard:
guard = create_guard([]) # start empty
guard.add_rule("when action contains 'delete' then block")
guard.add_rule("when spend gt 100 then require_approval")
print(guard.list_rules()) # ["when action...", "when spend..."]
guard.check({"action": "delete_user"})
guard.check({"action": "read_logs"})
print(guard.get_log()) # [{timestamp, context, result}, ...] Handle blocked actions
Two patterns: check the result, or use the decorator.
from savants_guard import create_guard, GuardError
guard = create_guard(["when action eq 'destroy' then block"])
# Option 1: check() returns a result (never throws)
result = guard.check({"action": "destroy"})
if result.blocked:
print(f"Blocked by: {result.rule}")
# Option 2: @guard.wrap raises GuardError on block
@guard.wrap
def dangerous_action(**kwargs):
return "executed"
try:
dangerous_action(action="destroy")
except GuardError as e:
print(e.rule) # "when action eq 'destroy' then block"
print(e.guard_action) # "block"
print(e.context) # {"action": "destroy"} Implement approval workflows
The require_approval action doesn't block automatically. It tells you the action needs human approval. You decide what to do:
const guard = createGuard([
"when spend gt 100 then require_approval",
]);
const result = guard.check({ action: 'purchase', spend: 250 });
if (result.action === 'require_approval') {
// Your approval logic here:
const approved = await askManager(`Approve $250 purchase?`);
if (!approved) throw new Error('Purchase not approved');
}
// result.blocked is true for require_approval
// result.action tells you WHY it was blocked
// "block" = hard stop, "require_approval" = needs human, "alert" = log it Or use a preset
Don't want to write rules? Use a preset that covers common scenarios:
Python
from savants_guard import production_safety, spend_limit
guard = production_safety()
result = guard.check({"action": "delete_user", "environment": "production"})
print(result.blocked) # True
guard = spend_limit(100)
result = guard.check({"amount": 250})
print(result.blocked) # True TypeScript
import { productionSafety } from '@savants.dev/guard';
const guard = productionSafety();
const tools = guard.wrapTools(myTools); | Preset | Python | TypeScript | What it blocks |
|---|---|---|---|
| Production safety | production_safety() | productionSafety() | delete, terminate, drop, remove in production |
| Spend limit | spend_limit(100) | spendLimit(100) | amount, spend, or cost exceeding threshold |
| Business hours | business_hours() | businessHours() | all actions on Saturday and Sunday |
| Deploy safety | deploy_safety() | deploySafety() | risky deploys: Friday + high risk, low test pass rate |
Composable profiles (standard, secrets, k8s-safe, etc.) are available via the CLI: savants guard preset standard+secrets. The Python/TypeScript SDKs use the function presets above, or you can load profile JSON files directly with create_guard(json.load(open("standard.json"))).
Test a guard
Verify your rules work before deploying:
const guard = createGuard(["when spend gt 100 then block"]);
guard.check({ spend: 150 }); // { blocked: true, action: "block" }
guard.check({ spend: 50 }); // { blocked: false }
guard.check({ spend: 100 }); // { blocked: false } — gt means greater than, not gte What just happened?
You defined deterministic rules that your AI agent cannot bypass.
- Guards are code, not prompts. The LLM never sees them.
- They run BEFORE every action. Blocked = the function never executes.
- No prompt injection can disable them. No hallucination can bypass them.
- Works with any LLM: Claude, GPT, Gemini, Llama, or your own model.
- 100% local. No network calls. No Savants account required.
Scale with your team (optional)
Your guards work locally. When your team needs central rule management, live updates, and a dashboard showing what was blocked:
// Step 1: Sign up at savants.cloud/activate (Google or GitHub OAuth)
// Step 2: Create an API key in the dashboard
// Step 3: Connect your SDK to the cloud:
const guard = await createGuard(
["when action contains 'delete' then block"], // local rules (still work)
{
managed: true,
apiKey: process.env.SAVANTS_API_KEY, // from dashboard
}
);
// SDK fetches latest rules from cloud on startup
// Polls for changes every 30s (no redeploy needed)
// Reports events to dashboard (async, non-blocking)
// Local rules + cloud rules merge — local evaluates first Free → Pro in 3 steps: Sign up at savants.cloud/activate, create an API key, add managed: true to your guard. Your existing local rules keep working. Cloud rules are additive.
API keys for your team
Each developer creates their own API key from the dashboard, or share one org key via your secrets manager:
# .env (each developer or CI/CD)
SAVANTS_API_KEY=sk_live_your_key_here
# Or use your secrets manager:
# GitHub Actions: secrets.SAVANTS_API_KEY
# Vercel: Environment Variables
# AWS: Secrets Manager / SSM Parameter Store What happens if the cloud is unreachable?
The SDK caches the last-fetched rule bundle in memory. If a poll fails, it silently keeps using cached rules. Your guards never stop working.
| Scenario | What happens |
|---|---|
| Cloud is up | SDK fetches latest rules on startup, polls every 30s |
| Cloud goes down after startup | SDK uses cached rules. Guards keep evaluating locally. |
| Cloud is down on startup | SDK uses local rules only (from your code). Warns in console. |
| Cloud comes back | Next poll picks up latest rules automatically. |
How rules merge
Local rules (in your code) and cloud rules (from the dashboard) work together:
- Local rules evaluate first — fast fail, no network needed
- Cloud rules evaluate second — additive, managed centrally
- No conflicts possible — rules are independent. Each checks the context and returns block/allow.
- First blocking rule wins — if any rule (local or cloud) blocks, the action is blocked.
What the dashboard shows
After signing up at savants.cloud/activate:
- Overview — system health, open issues, agent status
- Guard rules — add, edit, and remove rules via API (
POST /api/v1/guard/rules) - Audit log — every action your agents attempted, what was blocked, which rule fired
- API keys — create and manage keys for your team
- Team — invite members, assign roles (admin/member/viewer)
- Billing — current plan, usage, upgrade/downgrade
What's next?
You have working guardrails. Here's where to go from here:
Also available: Savants CLI for code intelligence. curl -fsSL savants.sh | sh gives your AI agent 37 MCP tools for semantic code search, error diagnosis, and infrastructure monitoring. Learn more
Uninstall
To remove Savants from your system:
# macOS / Linux
curl -fsSL releases.savants.dev/latest/uninstall.sh | sh
# Windows (PowerShell)
irm releases.savants.dev/latest/uninstall.ps1 | iex This removes the savants binary and cached data from ~/.savants/. Your guard rules and profiles are preserved unless you delete them manually.