Content Guardrails

Overview

Content Guardrails are a lightweight, fully local content-filter layer that sits between you and the model. They let you block or scrub specific content on the way in (your message, before it reaches the LLM) and on the way out (the assistant's reply, after it streams). Think of it as a regex-first, locally graded counterpart to hosted moderation features like Bedrock Guardrails or Gemini safety settings — but nothing leaves your machine.

Typical reasons to turn this on:

Guardrails are off by default. They only activate when you create ~/.evi/guardrails.toml with enabled = true and at least one rule.

This is a guardrail, not airtight security. A determined model or user can phrase around a regex. It is meant for hygiene and shared-machine policy, not as a hard security boundary.

How it works

Guardrails load from a single TOML file and apply to text in a direction:

Three rule kinds layer together and run in a fixed order, stopping at the first block:

  1. [[rule]] — regex. Fast, deterministic, fully local. Can block or redact. Regexes are compiled and validated at load time; invalid ones are silently skipped.
  2. [[judge]] — semantic, LLM-graded. eVi's own model classifies the text against a plain-English policy you write and blocks on a match. It runs as a single, separate, non-streaming model round-trip per turn (temperature 0, no tools), so it stays local. Block-only (no redact).
  3. [[classifier]] — offline ML model. A local HuggingFace text-classification model scores the text; eVi blocks when a score for one of your labels crosses threshold. Fully offline after the first download. Block-only.

Important behavior of the two semantic layers (judge and classifier):

Setup

Config file

All guardrail config lives in one file:

Top-level key:

KeyTypeDefaultNotes
enabledbooltrueMaster switch. Even with enabled = true, guardrails stay off unless at least one rule/judge/classifier is defined.

[[rule]] — regex rules

KeyDefaultNotes
pattern(required)A Python re regex. Empty/invalid patterns are skipped.
namefirst 24 chars of patternLabel shown in listings/warnings.
action"block"block or redact. Anything else falls back to block.
applies_to"both"input, output, or both. Anything else falls back to both.

[[judge]] — semantic LLM rules

KeyDefaultNotes
policy(required)Plain-English description of what's disallowed. Empty → rule skipped.
namefirst 24 chars of policyLabel.
applies_to"both"input, output, or both. Block-only.

[[judge]] rules use your configured chat model ([llm] model in config.toml) as the grader — no extra setup, but they add one model round-trip per turn they run on.

[[classifier]] — offline ML rules

KeyDefaultNotes
model"" → unitary/toxic-bertAny HuggingFace text-classification model id.
name"classifier"Label.
labels[] (any label)Lower-cased; block when any listed label crosses threshold. [] = block on any label.
threshold0.5Score (0–1) at/above which the rule blocks.
applies_to"both"input, output, or both. Block-only.

Optional dependency. [[classifier]] rules need transformers + torch:

pip install 'evi-assistant[moderation]'

The model loads lazily (first use downloads weights; cached afterward) and runs fully offline thereafter. If the deps or model aren't available, classifier rules simply fail open and are skipped. Downloaded model weights live under ~/.evi/models/.

Usage

CLI — evi guardrails

There are three subcommands for inspecting and dry-running your config (none of them edit the file — you hand-edit guardrails.toml):

evi guardrails path                       # print the config file path
evi guardrails list                       # list loaded rules + enabled state
evi guardrails test "<text>"              # dry-run text through the rules
evi guardrails test "<text>" --direction input    # input | output | both (default both)

evi guardrails test shows the verdict (allowed / BLOCKED), which rules blocked or redacted, and the redacted result text. Note that test exercises the regex layer only — it does not invoke the [[judge]] or [[classifier]] graders.

In chat (CLI and Web)

Guardrails are applied automatically whenever they're enabled — there's no per-turn flag to toggle. Both the interactive CLI chat and the web UI load ~/.evi/guardrails.toml at session start and wire it into the agent.

There is no slash command to enable/disable guardrails — control them by editing the config file (and enabled).

Web / Desktop — the Guardrails editor

The web and desktop apps have a Settings → Guardrails panel that shows the enabled state and a rule-count summary (regex / judge / classifier), plus a guardrails.toml editor. Save validates the TOML server-side (each [[rule]] regex must compile and each [[judge]] must have a policy) and reports the error inline rather than writing a broken file. It is backed by GET/POST /api/guardrails.

Examples

Example 1 — Block secrets on input, redact emails both ways

~/.evi/guardrails.toml:

enabled = true

[[rule]]
name = "block-secrets"
pattern = "(?i)(api[_-]?key|secret|password)\\s*[:=]"
action = "block"          # refuse the whole turn — never send it to the model
applies_to = "input"

[[rule]]
name = "redact-emails"
pattern = "[\\w.+-]+@[\\w-]+\\.[\\w.-]+"
action = "redact"         # mask the match, then continue
applies_to = "both"

Verify it loaded and dry-run some text:

$ evi guardrails list
guardrails: enabled

  block-secrets block (input) — /(?i)(api[_-]?key|secret|password)\s*[:=]/
  redact-emails redact (both) — /[\w.+-]+@[\w-]+\.[\w.-]+/

$ evi guardrails test "my api_key= sk-12345 and email is me@example.com"
input: BLOCKED
  blocked by: block-secrets

$ evi guardrails test "ping me@example.com about the docs" --direction output
output: allowed
  redacted by: redact-emails
  result: ping [REDACTED] about the docs

Example 2 — Semantic judge + offline toxicity classifier

~/.evi/guardrails.toml:

enabled = true

[[judge]]                  # graded by your local LLM, no extra install
name = "no-self-harm"
policy = "Requests for, or content encouraging, self-harm or suicide."
applies_to = "both"

[[classifier]]             # offline HF model; needs evi-assistant[moderation]
name = "toxicity"
model = "unitary/toxic-bert"            # "" also resolves to this default
labels = ["toxic", "threat", "insult"]  # [] would block on ANY returned label
threshold = 0.7
applies_to = "both"

Install the optional classifier deps, then confirm the rules are loaded:

$ pip install 'evi-assistant[moderation]'

$ evi guardrails list
guardrails: enabled

  no-self-harm judge (both) — Requests for, or content encouraging, self-harm or…
  toxicity classifier (both) — unitary/toxic-bert >= 0.7 on toxic, insult, threat

These two layers run live during chat (evi chat or the web UI). Remember that evi guardrails test only checks regex rules, so it will report allowed for text that the judge or classifier would catch at runtime — exercise those in an actual chat turn.

Notes / limits

Edit on GitHub ↗