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-guard or npm 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:

FieldTypeDescription
blockedboolTrue for block and require_approval actions. False for everything else.
allowedboolTrue only when no rule matched at all. False when any rule matched (even soft actions).
actionstr | NoneThe action from the matched rule: "block", "suggest", "rewrite", "ask", "require_approval", or None
suggestionstr | NoneThe message from suggest, replacement from rewrite, or reason from ask. None if no message or no match.
rulestr | NoneThe full DSL rule that matched, or None.
contextdictThe 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:

ActionDSL syntaxBehaviorresult.blocked
suggestthen suggest 'Use chmod 755'Denies action, provides alternative. Agent auto-recovers.False
rewritethen rewrite 'git push --force-with-lease'Silently replaces the command. Agent never sees original.False
askthen ask 'Deploy requires approval'Escalates to user for approval before proceeding.False
blockthen blockHard 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);
PresetPythonTypeScriptWhat it blocks
Production safetyproduction_safety()productionSafety()delete, terminate, drop, remove in production
Spend limitspend_limit(100)spendLimit(100)amount, spend, or cost exceeding threshold
Business hoursbusiness_hours()businessHours()all actions on Saturday and Sunday
Deploy safetydeploy_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.

ScenarioWhat happens
Cloud is upSDK fetches latest rules on startup, polls every 30s
Cloud goes down after startupSDK uses cached rules. Guards keep evaluating locally.
Cloud is down on startupSDK uses local rules only (from your code). Warns in console.
Cloud comes backNext poll picks up latest rules automatically.

How rules merge

Local rules (in your code) and cloud rules (from the dashboard) work together:

  1. Local rules evaluate first — fast fail, no network needed
  2. Cloud rules evaluate second — additive, managed centrally
  3. No conflicts possible — rules are independent. Each checks the context and returns block/allow.
  4. 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:

What's next?

You have working guardrails. Here's where to go from here:

Guard DSL Reference All 16 operators: eq, gt, contains, matches, and more Recipes Spend limits, Friday deploy block, SOC 2 audit Try it live Write and run guard rules in your browser Scale with your team Live rule updates, audit dashboard, org-wide policies

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.