Veto/docs

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 LLM

The action is inferred from intent keywords: block/prevent/stopblock, warn/alertwarn, ask/approve/confirmrequire_approval, log/monitorlog. 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");      // false

Use 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 generation

This 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 operators

sanitizeGeneratedRules

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 severity values default to "medium" with a warning
  • Invalid action values 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 };
}