Validation Modes
How Veto validates tool calls — local, cloud, API, custom, and kernel modes.
Veto supports five validation modes. The SDK auto-detects the mode from your init options and environment. You can also set it explicitly in veto.config.yaml:
validation:
mode: "local" # "local" | "cloud" | "api" | "custom" | "kernel"Local mode (default)
Evaluates YAML rules from ./veto/rules/*.yaml entirely in-process. No network calls, no API key, no account.
# No validation.mode needed — local is the default
version: "1.0"
mode: "strict"Supports all YAML condition operators: equals, not_equals, contains, not_contains, starts_with, ends_with, matches, in, not_in, greater_than, less_than, within_hours, outside_hours. Also supports expression-based conditions from the compiled rule format.
Activated automatically when no apiKey, endpoint, or explicit validation.mode is configured.
See SDK Modes for the full auto-detection precedence.
Cloud mode
Routes validation through the Veto Cloud API. Policies are managed in the dashboard — the SDK fetches and caches them automatically.
validation:
mode: "cloud"
cloud:
apiKey: "veto_abc123..." # or set VETO_API_KEY env var
baseUrl: "https://api.runveto.com"
timeout: 30000
retries: 2
approval:
pollInterval: 2000 # ms between polls
timeout: 300000 # max wait (5 min)Three validation paths
Cloud mode uses the fastest validation path that can handle each policy:
| Path | Where it runs | Latency | When it triggers |
|---|---|---|---|
| Client-side deterministic | SDK (local) | ~1-5ms | Policy is cached, mode is deterministic, no session/rate constraints |
| Server-side deterministic | Veto Cloud | ~30-50ms | Cache miss, or policy has session constraints or rate limits |
| Server-side LLM | Veto Cloud | ~500-2000ms | Policy mode is llm |
The SDK picks the path automatically. You configure the policy — the routing is handled for you.
Client-side deterministic
When the SDK has a cached deterministic policy with no session or rate constraints, it evaluates constraints locally. No network call. Decisions are logged to the server asynchronously via POST /v1/decisions so the dashboard stays accurate.
Supported constraints: minimum, maximum, greaterThan, lessThan, enum, regex, minLength, maxLength, minItems, maxItems, required, notNull. See Constraints Reference.
Server-side deterministic
Same constraint types, plus stateful checks that require server coordination:
- Session constraints — e.g. "max 3 calls to
delete_recordper session" - Rate limits — e.g. "max 10 calls per minute"
- Cross-tool constraints — e.g. "if
read_filewas called, blocksend_email"
Server-side LLM
An LLM evaluates the tool call against natural language policies. Use this for checks that can't be expressed as static constraints:
- Semantic evaluation against a policy description
- Exception lists (e.g. "deny transfers to external accounts, except for verified vendors")
- Complex multi-argument relationships
- Context-dependent decisions
Three decisions
The cloud can return three decisions:
| Decision | What happens |
|---|---|
allow | Tool call proceeds |
deny | ToolCallDeniedError is thrown |
require_approval | SDK pauses and polls until a human approves or denies in the dashboard |
Policy cache
Policies are cached with stale-while-revalidate:
| Window | Duration | Behavior |
|---|---|---|
| Fresh | 0–60s | Serve from cache |
| Stale | 60s–5min | Serve from cache, refresh in background |
| Expired | >5min | Fall through to server |
Policy changes in the dashboard propagate to all connected SDKs within 60 seconds.
Event webhooks
You can optionally emit webhook notifications after key validation outcomes:
events:
webhook:
url: "https://hooks.example.com/veto"
on: ["deny", "require_approval", "budget_exceeded"]
min_severity: "high"
format: "slack" # slack | pagerduty | generic | cefWebhook delivery is fire-and-forget and does not block validation responses. If webhook delivery fails, Veto logs a warning and returns the original validation decision.
For payload formats and end-to-end examples, see Event Webhooks.
API mode
Sends validation requests to a self-hosted endpoint. Use this when you want full control over the validation backend.
validation:
mode: "api"
api:
url: "http://localhost:8080"
endpoint: "/tool/call/check"The endpoint receives the tool name and arguments as JSON and must return { decision: "allow" | "deny" }.
Custom mode
Calls an LLM provider directly from the SDK. No server needed. Rules are evaluated using the LLM with the YAML rules in your veto/rules/ directory.
validation:
mode: "custom"
custom:
provider: "openai" # openai | anthropic | gemini
model: "gpt-4o-mini"| Provider | Models | Env variable |
|---|---|---|
openai | gpt-4o, gpt-4o-mini | OPENAI_API_KEY |
anthropic | claude-sonnet-4-5-20250929 | ANTHROPIC_API_KEY |
gemini | gemini-3-flash-preview | GEMINI_API_KEY |
Best for development and testing when you don't need the dashboard or audit trails.
Kernel mode
Uses a local Ollama model for fully offline validation. Zero external API calls.
validation:
mode: "kernel"
kernel:
url: "http://localhost:11434/v1"
model: "llama3"Requires Ollama running locally. Best for air-gapped environments.
Operating modes
Orthogonal to validation mode, Veto has three operating modes that control what happens when a tool call is denied:
mode: "strict" # or "log" or "shadow"| Mode | Behavior |
|---|---|
strict | Blocks denied calls — throws ToolCallDeniedError |
log | Logs denied calls but allows execution to continue |
shadow | Preserves real decisions for wrapped calls but never blocks execution |
Use shadow during initial rollout to observe what would be blocked without affecting execution. Switch to strict once you're confident in your policies.