DSPy runtime authorization
Wrap DSPy modules and ReAct agents with Veto. Each governed tool call composed by a DSPy program is evaluated before dispatch: allow, review, or deny, with an exportable decision record per governed decision.
Why DSPy needs guardrails
DSPy treats prompts as code. You declare a signature, compose modules, and an optimizer compiles few-shot examples. When a dspy.ReAct module is given tools, its compiled program decides which to call and with what arguments. The framework executes them: there is no guardrail boundary between the decision and the side effect.
The optimizer makes this riskier. Bootstrapping calls the model many times with training examples. If your tools touch real systems during compilation, every optimization run can fire production side effects. Without runtime authorization, a poisoned training example becomes a poisoned production agent.
DSPy modules call Python functions directly. No signature, type hint, or assertion can block "transfer $1M to attacker".
Bootstrapping invokes tools repeatedly. A single wrong example fans out across the compiled program.
DSPy's traces capture model calls. They do not produce a decision record per side effect.
Before and after Veto
The left tab shows a standard dspy.ReAct agent. The right tab adds Veto inside each tool function. Same module, same signatures, every call evaluated against policy first.
import os
import dspy
lm = dspy.LM(f"openai/{os.environ['OPENAI_MODEL']}")
dspy.configure(lm=lm)
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email to a recipient."""
return email_service.send(to, subject, body)
def transfer_funds(from_account: str, to_account: str, amount: float) -> str:
"""Move money between accounts."""
return treasury.transfer(from_account, to_account, amount)
def query_records(table: str, filter: str) -> str:
"""Query records from a table."""
return db.query(table, filter)
class FinanceAgent(dspy.Module):
def __init__(self):
super().__init__()
self.react = dspy.ReAct(
"request -> resolution: str",
tools=[send_email, transfer_funds, query_records],
)
def forward(self, request):
return self.react(request=request)
agent = FinanceAgent()
# DSPy's ReAct module composes tool calls through CoT reasoning.
# Every selected tool is invoked. No enforcement at the call site.
result = agent(request=user_request)Guarding through optimization
DSPy's compilers: BootstrapFewShot, MIPRO, COPRO: invoke tools repeatedly while searching for high-quality prompts. Because Veto sits inside the tool function, every optimizer-issued call is also guarded.
import os
import dspy
from dspy.teleprompt import BootstrapFewShot
from veto_sdk import Veto
veto = Veto(api_key=os.environ["VETO_API_KEY"])
class Trader(dspy.Signature):
"""Execute a trade order from a research note."""
note: str = dspy.InputField()
order: str = dspy.OutputField()
def execute_trade(symbol: str, side: str, shares: int) -> str:
"""Submit a buy or sell order."""
decision = veto.guard(
tool="execute_trade",
arguments={"symbol": symbol, "side": side, "shares": shares},
context={"market": "open", "strategy": "research-bot"},
)
if decision.decision != "allow":
return f"Blocked: {decision.reason}"
return broker.submit(symbol, side, shares)
trader = dspy.ReAct(Trader, tools=[execute_trade])
# DSPy optimizers compile prompts via bootstrapped few-shot examples.
# Veto guards each governed tool call during compilation AND production runs.
optimizer = BootstrapFewShot(metric=trade_quality_metric, max_bootstrapped_demos=8)
compiled_trader = optimizer.compile(trader, trainset=train_examples)
# After compilation the guards still apply at inference.
order = compiled_trader(note=research_note)Policy configuration
Declare what DSPy programs can and cannot do. Read the tool-call authorization glossary for the underlying primitives.
rules:
- name: cap_email_value_at_1k
description: Emails mentioning amounts over $1,000 need approval
tool: send_email
when: "args.body =~ /\$([1-9]\d{3,}|\d{4,})/"
action: require_approval
approvers: [compliance-team]
timeout: 30m
message: "Emails over $1,000 require policy review"
- name: block_external_transfers
description: Funds may only move between internal accounts
tool: transfer_funds
when: "!args.to_account.startsWith('internal-')"
action: deny
message: "External transfers must be issued by a human treasurer"
- name: cap_daily_transfer_volume
description: Per-day transfer cap of $50k
tool: transfer_funds
when: context.daily_transferred + args.amount > 50000
action: deny
message: "Daily transfer cap of $50,000 reached"
- name: restrict_pii_queries
description: Block reads against PII tables
tool: query_records
when: args.table in ["customers_pii", "ssn_map", "payroll"]
action: deny
message: "Access to PII tables prohibited for the research-bot agent"
- name: market_hours_only
description: Trades only during market hours
tool: execute_trade
when: context.time.hour < 9 || context.time.hour >= 16
action: deny
message: "Trading restricted to 09:30-16:00 ET"How Veto fits
Install the SDK
pip install veto-sdk dspy-aiDefine policies
Create veto/policies.yaml. Use the tool function name as the rule's tool matcher.
Guard each tool
Call veto.guard() at the guarded functions passed into dspy.ReAct. Policy stays at the module boundary.
Use cases
Finance research agents
DSPy finance agents that read research notes and propose trades. Cap notional value, restrict to allowlisted symbols, block execution outside market hours.
High-stakes email
Emails over a value threshold (e.g. $1,000 mentions) route to compliance approval. Veto detects dollar amounts in the body via regex before sending.
PII-aware retrieval
DSPy retrievers that query internal tables get policy-level allowlists. Block PII tables, restrict filters to indexed columns, record governed query.
Optimizer safety
Bootstrap and MIPRO runs invoke tools many times during compilation. Veto enforces the same policy at compile time as at inference, preventing optimizer-induced incidents.
Frequently asked questions
Does Veto work with dspy.ReAct and other tool-using modules?
Does Veto guard tools during optimizer compilation?
Can I pass DSPy traces to Veto for context?
How does this interact with DSPy assertions?
Related integrations
Wrap one DSPy tool path and inspect the decision record.