D

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.

No tool-level guard

DSPy modules call Python functions directly. No signature, type hint, or assertion can block "transfer $1M to attacker".

Optimizers run tools

Bootstrapping invokes tools repeatedly. A single wrong example fans out across the compiled program.

Audit gap

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.

py
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.

dspy_compiled_with_veto.py
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.

veto/policies.yaml
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

1

Install the SDK

pip install veto-sdk dspy-ai
2

Define policies

Create veto/policies.yaml. Use the tool function name as the rule's tool matcher.

3

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?
Yes. Veto guards inside any Python function you pass to a DSPy module. Whether the call originates from ReAct, ProgramOfThought, or a custom dspy.Module subclass, the integration point is the same: validate the call against policy before the side effect.
Does Veto guard tools during optimizer compilation?
Yes. DSPy optimizers like BootstrapFewShot invoke tool functions during compilation. Because Veto sits inside the tool body, the same policy can fire for compile-time and inference-time calls. This can prevent optimizer runs from triggering disallowed side effects on the governed path.
Can I pass DSPy traces to Veto for context?
You can pass the current trace context to veto.guard() via the context argument. Policies can condition on trace metadata: which signature triggered the call, which optimizer is running, which few-shot example produced the input.
How does this interact with DSPy assertions?
Assertions check post-hoc correctness of model outputs. Veto checks tool calls before they run. Different layers. An assertion can verify the final answer; Veto blocks the disallowed intermediate step that produced it.

Related integrations

Wrap one DSPy tool path and inspect the decision record.