Policy Generation
Generate Veto rules from natural language using the SDK's policy utilities.
The veto-sdk/policy module converts natural-language policy descriptions into structured Veto rules. It includes a system prompt for LLM-based generation, instant pattern matching for common intents, and validation/sanitization for LLM output.
This is the same policy engine used by the Veto browser extension.
Import
import {
tryInstantGeneration,
looksLikePolicyDeclaration,
sanitizeGeneratedRules,
validatePolicyOutput,
reviewPolicyRequest,
BROWSER_AGENT_SYSTEM_PROMPT,
getPolicyOutputSchema,
} from 'veto-sdk/policy';Instant generation
tryInstantGeneration returns rules synchronously for six well-known patterns — no LLM call, no latency. Returns null for anything it doesn't recognize.
Recognized patterns: credit card, PII, government ID, API key/secret, price limit, salary figures.
const result = tryInstantGeneration('block everything when credit cards are visible');
// result.rules[0].id === 'instant-credit-card-shield'
// result.rules[0].conditions[0].field === 'arguments.extracted_entities.has_credit_cards'
const priceResult = tryInstantGeneration('require approval for purchases over $500');
// priceResult.rules[0].id === 'instant-price-limit-500'
// priceResult.rules[0].action === 'require_approval'
// priceResult.rules[0].conditions[0] === { field: 'arguments.extracted_entities.max_price', operator: 'greater_than', value: 500 }
const unknown = tryInstantGeneration('only click elements with a red border');
// unknown === null → needs LLMThe action is inferred from intent keywords: block/prevent/stop → block, warn/alert → warn, ask/approve/confirm → require_approval, log/monitor → log. Default is block.
Policy declaration detection
looksLikePolicyDeclaration detects whether user input is a standing policy rule (should route to generation) rather than a one-off automation instruction.
looksLikePolicyDeclaration("never submit forms on banking sites"); // true
looksLikePolicyDeclaration("don't click anything unless I approve it"); // true
looksLikePolicyDeclaration("require my approval before purchases"); // true
looksLikePolicyDeclaration("go to acme.com and fill out the form"); // falseUse this to decide whether to send user input to the policy generator or the automation loop.
LLM-based generation
For anything tryInstantGeneration returns null on, use BROWSER_AGENT_SYSTEM_PROMPT as the system message. The LLM returns JSON with rules and explanation fields.
import OpenAI from 'openai';
import { BROWSER_AGENT_SYSTEM_PROMPT, validatePolicyOutput } from 'veto-sdk/policy';
const client = new OpenAI();
async function generatePolicy(userInput: string) {
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: BROWSER_AGENT_SYSTEM_PROMPT },
{ role: 'user', content: userInput },
],
response_format: { type: 'json_object' },
});
const raw = JSON.parse(response.choices[0].message.content!);
return validatePolicyOutput(raw);
}
const result = await generatePolicy('block all actions on social media sites');
if (result.success) {
console.log(result.rules); // Rule[]
console.log(result.explanation); // plain-English summary
console.log(result.warnings); // any unknown tools/operators
}Clarification detection
reviewPolicyRequest checks if a policy description is ambiguous enough that generating rules would silently encode the wrong behavior. Returns null when the request is clear.
const clarification = reviewPolicyRequest('block social media after 10 minutes today');
if (clarification) {
// Show clarification.questions to the user before generating
console.log(clarification.questions);
// e.g. "Which domains should count as social media for this rule?..."
// "Should that time limit apply per domain or across all social sites combined?..."
}
const clear = reviewPolicyRequest('block purchases over $200');
// clear === null → proceed to generationThis runs synchronously before any LLM call. Call it first in your generation flow.
Validation and sanitization
validatePolicyOutput
Validates raw LLM JSON against the policy schema (requires zod) and sanitizes it into Rule[] objects. Use this when you call the LLM yourself and have the raw parsed response.
const raw = JSON.parse(llmResponseText);
const result = await validatePolicyOutput(raw);
if (!result.success) {
console.error(result.error); // Zod parse error message
return;
}
// result.rules — Rule[] with normalized IDs (prefixed 'local-nl-')
// result.warnings — non-fatal issues like unknown tools or operatorssanitizeGeneratedRules
Lower-level alternative to validatePolicyOutput — skips Zod validation and just normalizes already-parsed output. Use this when you've validated the shape yourself or are using a structured output API with a guaranteed schema.
import { sanitizeGeneratedRules } from 'veto-sdk/policy';
const { rules, warnings } = sanitizeGeneratedRules(parsedOutput);Sanitization behavior:
- Rule IDs are normalized to
local-nl-<kebab-id>and deduplicated - Invalid
severityvalues default to"medium"with a warning - Invalid
actionvalues default to"block"with a warning - Unknown tools and operators are kept (passed through to cloud evaluation) with a warning
- Tags default to
['nl-generated']if omitted
Full generation flow
import {
tryInstantGeneration,
looksLikePolicyDeclaration,
reviewPolicyRequest,
validatePolicyOutput,
BROWSER_AGENT_SYSTEM_PROMPT,
} from 'veto-sdk/policy';
async function handlePolicyInput(userInput: string) {
// 1. Check if this is actually a policy declaration
if (!looksLikePolicyDeclaration(userInput)) {
return { type: 'automation', input: userInput };
}
// 2. Check for ambiguity before generating
const clarification = reviewPolicyRequest(userInput);
if (clarification) {
return { type: 'clarification', questions: clarification.questions };
}
// 3. Try instant generation (zero latency)
const instant = tryInstantGeneration(userInput);
if (instant) {
return { type: 'rules', rules: instant.rules, explanation: instant.explanation };
}
// 4. Fall back to LLM
const response = await callLLM(BROWSER_AGENT_SYSTEM_PROMPT, userInput);
const result = await validatePolicyOutput(JSON.parse(response));
return { type: 'rules', ...result };
}