This guide will get you from zero to a working Veto-protected agent in under 5 minutes.
Prerequisites
Node.js 18+ or Python 3.10+
An AI agent project (or start fresh)
API key for your LLM provider (OpenAI, Anthropic, or Google)
Choose your language
Install Veto Initialize Veto Run veto init to create the configuration structure: This creates:
./veto/veto.config.yaml — Main configuration file
./veto/rules/defaults.yaml — Default rules
./veto/.env.example — Example environment variables
Add guardrails to your agent Here’s a complete example using LangChain: import { Veto } from 'veto-sdk' ;
import { createAgent , tool } from 'langchain' ;
import { z } from 'zod' ;
// Define your tools
const transferFunds = tool (
({ amount , from_account , to_account }) =>
JSON . stringify ({
success: true ,
amount ,
from_account ,
to_account ,
tx_id: 'TX-' + Date . now ()
}),
{
name: 'transfer_funds' ,
description: 'Transfer money between bank accounts' ,
schema: z . object ({
amount: z . number (). describe ( 'Amount to transfer in USD' ),
from_account: z . string (). describe ( 'Source account ID' ),
to_account: z . string (). describe ( 'Destination account ID' ),
}),
}
);
const getBalance = tool (
({ account_id }) =>
JSON . stringify ({ account_id , balance: 25000 , currency: 'USD' }),
{
name: 'get_balance' ,
description: 'Get the balance of a bank account' ,
schema: z . object ({
account_id: z . string (). describe ( 'The account ID' ),
}),
}
);
async function main () {
// 1. Initialize Veto (loads ./veto/veto.config.yaml + rules)
const veto = await Veto . init ();
// 2. Wrap tools — types preserved, validation injected
const tools = veto . wrap ([ getBalance , transferFunds ]);
// 3. Create agent with wrapped tools
const agent = createAgent ({
model: 'google-genai:gemini-2.0-flash' ,
tools ,
});
// 4. Use your agent normally
const result = await agent . invoke ({
messages: [{ role: 'user' , content: 'Transfer $500 from ACC-001 to ACC-002' }],
});
console . log ( result . messages [ result . messages . length - 1 ]. content );
// View stats
console . log ( veto . getHistoryStats ());
}
main (). catch ( console . error );
Edit veto/rules/defaults.yaml to add your guardrails: version : "1.0"
name : financial-rules
rules :
- id : block-large-transfers
name : Block Large Transfers
description : Transfers over $10,000 require manual approval
enabled : true
action : block
tools : [ transfer_funds ]
conditions :
- expression : "arguments.amount > 10000"
- id : block-external-transfers
name : Block External Transfers
description : Block transfers to external banks
enabled : true
action : block
tools : [ transfer_funds ]
conditions :
- expression : "arguments.to_account starts_with 'EXT-'"
Test it out Run your agent: Try asking it to transfer $50,000 or send money to an account starting with EXT-. Veto will block these actions based on your rules.
Install Veto With LLM provider support: pip install veto[openai] # OpenAI
pip install veto[anthropic] # Anthropic
pip install veto[gemini] # Google Gemini
pip install veto[all] # All providers
Initialize Veto Run veto init to create the configuration structure: This creates:
./veto/veto.config.yaml — Main configuration file
./veto/rules/defaults.yaml — Default rules
./veto/.env.example — Example environment variables
Add guardrails to your agent Here’s a complete example: import asyncio
import json
from veto import Veto, ToolCallDeniedError
# Define your tools
class TransferFunds :
def __init__ ( self ):
self .name = "transfer_funds"
self .description = "Transfer money between bank accounts"
async def handler ( self , args : dict ) -> str :
return json.dumps({
"success" : True ,
"amount" : args[ "amount" ],
"from_account" : args[ "from_account" ],
"to_account" : args[ "to_account" ],
"tx_id" : f "TX- { args[ 'amount' ] } "
})
class GetBalance :
def __init__ ( self ):
self .name = "get_balance"
self .description = "Get the balance of a bank account"
async def handler ( self , args : dict ) -> str :
return json.dumps({
"account_id" : args[ "account_id" ],
"balance" : 25000 ,
"currency" : "USD"
})
async def main ():
# 1. Initialize Veto (loads ./veto/veto.config.yaml + rules)
veto = await Veto.init()
# 2. Wrap tools — validation injected
tools = [GetBalance(), TransferFunds()]
wrapped_tools = veto.wrap(tools)
# 3. Use tools in your agent
# This will be ALLOWED
try :
result = await wrapped_tools[ 1 ].handler({
"amount" : 500 ,
"from_account" : "ACC-001" ,
"to_account" : "ACC-002"
})
print ( f "✅ Transfer allowed: { result } " )
except ToolCallDeniedError as e:
print ( f "🛑 Transfer blocked: { e.reason } " )
# This will be BLOCKED (amount > $10,000)
try :
result = await wrapped_tools[ 1 ].handler({
"amount" : 50000 ,
"from_account" : "ACC-001" ,
"to_account" : "ACC-003"
})
print ( f "✅ Transfer allowed: { result } " )
except ToolCallDeniedError as e:
print ( f "🛑 Transfer blocked: { e.reason } " )
# View stats
stats = veto.get_history_stats()
print ( f " \n Stats: { stats.total_calls } calls, { stats.allowed_calls } allowed, { stats.denied_calls } denied" )
if __name__ == "__main__" :
asyncio.run(main())
Edit veto/rules/defaults.yaml to add your guardrails: version : "1.0"
name : financial-rules
rules :
- id : block-large-transfers
name : Block Large Transfers
description : Transfers over $10,000 require manual approval
enabled : true
action : block
tools : [ transfer_funds ]
conditions :
- expression : "arguments.amount > 10000"
- id : block-external-transfers
name : Block External Transfers
description : Block transfers to external banks
enabled : true
action : block
tools : [ transfer_funds ]
conditions :
- expression : "arguments.to_account starts_with 'EXT-'"
Test it out Run your agent: The first transfer (500 ) w i l l s u c c e e d . T h e s e c o n d t r a n s f e r ( 500) will succeed. The second transfer ( 500 ) w i ll s u ccee d . T h eseco n d t r an s f er ( 50,000) will be blocked by Veto.
What just happened?
Initialized Veto
Veto.init() loaded your configuration from ./veto/veto.config.yaml and all rules from ./veto/rules/.
Wrapped your tools
veto.wrap() injected validation middleware into each tool’s execution handler. Types are preserved (TypeScript only).
Rules enforced automatically
When your agent called transfer_funds with amount > $10,000, Veto evaluated the conditions and blocked the call before execution.
Understanding rule actions
Veto supports five action types:
Action Behavior Use case blockDenies execution, throws error Hard limits (e.g., never delete production data) allowExplicitly allows execution Whitelist specific tool calls warnLogs warning but allows Soft limits for monitoring logSilently logs for audit Compliance tracking askRoutes to human approval queue Sensitive actions requiring review
The ask action requires configuring an approval callback URL in veto.config.yaml. See the human-in-the-loop guide for details.
Check policy enforcement
Use the CLI to test your rules without running your agent:
npx veto-cli guard check --tool transfer_funds --args '{"amount": 50000}' --json
Output:
{
"decision" : "block" ,
"rule" : "block-large-transfers" ,
"reason" : "amount 50000 > threshold 10000"
}
View audit history
Export decision history for compliance:
const json = veto . exportDecisions ( "json" );
const csv = veto . exportDecisions ( "csv" );
Next steps
Installation guide System requirements and advanced installation options
Configuration Deep dive into veto.config.yaml and rule syntax
Rule reference Complete reference for writing rules
Framework examples LangChain, Vercel AI SDK, OpenAI, Anthropic, and more
Production tip : Always test your rules in shadow mode first (mode: "log") to validate they work as expected before switching to strict mode (mode: "strict").