trailofbits / trailofbits/buttercup
Migrate Patcher from Manual Prompts to DSPy Framework
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.7k
- Forks
- 185
- Avg merge
- 51m
- Merged PRs (30d)
- 1
Description
Migrate Patcher from Manual Prompts to DSPy Framework
Background
The Buttercup patcher currently uses a sophisticated multi-agent system built with LangGraph to automatically generate patches for security vulnerabilities. While effective, the system relies heavily on manually crafted prompts that are:
- Brittle and hard to maintain - All prompts are hardcoded strings requiring manual updates
- Difficult to optimize - No systematic way to improve prompt effectiveness
- Inconsistent in output handling - Regex-based parsing prone to failures
- Limited in reliability - Simple fallback mechanisms without adaptive optimization
DSPy (Declarative Self-improving Python) offers a paradigm shift from manual prompt engineering to programmatic, optimizable LLM applications. Instead of writing brittle prompt strings, DSPy allows us to:
- Define task signatures declaratively
- Automatically optimize prompts using algorithms like MIPROv2
- Compose modular, reusable components
- Systematically improve performance with data-driven optimization
Current Architecture Analysis
Agent Structure
The patcher consists of 7 specialized agents orchestrated through LangGraph:
- InputProcessingAgent - Initial vulnerability data processing
- ContextRetrieverAgent - Code context extraction (694-1357 lines!)
- RootCauseAgent - Vulnerability root cause analysis
- SWEAgent - Patch strategy and creation
- QEAgent - Quality engineering and validation
- ReflectionAgent - Failure analysis and recovery
- PatcherLeaderAgent - Overall orchestration
Key Pain Points
1. Manual Prompt Templates
- RootCauseAgent: 60-line hardcoded system prompt (
rootcause.py:41-100) - ReflectionAgent: 169-line complex reflection prompt (
reflection.py:47-216) - SWEAgent: Separate 60-line strategy + 70-line creation prompts (
swe.py:49-190) - ContextRetrieverAgent: Multiple specialized prompts for different tasks
2. Complex Output Parsing
- Regex-based extraction throughout (
common.py:491-503,reflection.py:744-750) - Manual XML tag parsing with frequent failures
- No structured output guarantees
3. Limited Optimization
- No systematic prompt tuning capability
- Manual trial-and-error for improvements
- No data-driven optimization feedback loop
DSPy Migration Opportunity
High-Impact Transformations
1. Root Cause Analysis Module
Current: 60-line manual prompt with complex instructions
# Before: rootcause.py:41-100
ROOT_CAUSE_SYSTEM_MSG = """You are an expert...""" # 60 lines
DSPy Approach:
class RootCauseSignature(dspy.Signature):
"""Analyze vulnerability to identify root cause."""
stack_trace: str = dspy.InputField(desc="crash stack trace")
code_context: str = dspy.InputField(desc="relevant code snippets")
diff_context: str = dspy.InputField(desc="recent code changes")
root_cause: str = dspy.OutputField(desc="root cause analysis")
vulnerable_lines: list[int] = dspy.OutputField(desc="vulnerable line numbers")
class RootCauseAnalyzer(dspy.Module):
def __init__(self):
self.analyze = dspy.ChainOfThought(RootCauseSignature)
def forward(self, stack_trace, code_context, diff_context):
return self.analyze(stack_trace=stack_trace,
code_context=code_context,
diff_context=diff_context)
2. Patch Generation Pipeline
Current: Separate strategy + creation with manual chaining
# Before: swe.py - manual prompt construction across 200+ lines
DSPy Approach:
class PatchStrategySignature(dspy.Signature):
"""Determine optimal patching strategy."""
root_cause: str = dspy.InputField()
code_context: str = dspy.InputField()
strategy: str = dspy.OutputField(desc="patch strategy")
confidence: float = dspy.OutputField(desc="confidence score 0-1")
class PatchCreationSignature(dspy.Signature):
"""Generate concrete patch code."""
strategy: str = dspy.InputField()
vulnerable_code: str = dspy.InputField()
patch_diff: str = dspy.OutputField(desc="unified diff format patch")
class PatchGenerator(dspy.Module):
def __init__(self):
self.strategize = dspy.ChainOfThought(PatchStrategySignature)
self.create = dspy.Predict(PatchCreationSignature)
def forward(self, root_cause, code_context, vulnerable_code):
strategy = self.strategize(root_cause=root_cause,
code_context=code_context)
patch = self.create(strategy=strategy.strategy,
vulnerable_code=vulnerable_code)
return patch
3. Reflection with Adaptive Learning
Current: 169-line hardcoded 12-step process
# Before: reflection.py:47-216 - massive manual prompt
DSPy Approach:
class ReflectionSignature(dspy.Signature):
"""Analyze patch failure and suggest improvements."""
failure_info: str = dspy.InputField(desc="build/test failure details")
previous_attempts: list[str] = dspy.InputField(desc="prior patch attempts")
analysis: str = dspy.OutputField(desc="failure analysis")
next_action: str = dspy.OutputField(desc="recommended action")
should_retry: bool = dspy.OutputField(desc="whether to retry")
class AdaptiveReflection(dspy.Module):
def __init__(self):
self.reflect = dspy.ReAct(ReflectionSignature,
tools=[analyze_build_log,
check_test_results,
examine_patch_diff])
def forward(self, failure_info, previous_attempts):
return self.reflect(failure_info=failure_info,
previous_attempts=previous_attempts)
Optimization Strategy with MIPROv2
DSPy's MIPROv2 optimizer can automatically tune our modules using historical patch success data:
# Optimization configuration
from dspy.teleprompt import MIPROv2
# Prepare training data from successful patches
train_data = load_successful_patches() # Historical success cases
val_data = load_validation_patches() # Hold-out validation set
# Define metric
def patch_success_metric(gold, pred, trace=None):
"""Evaluate patch quality."""
builds = check_build_success(pred.patch_diff)
fixes_pov = check_pov_fixed(pred.patch_diff)
tests_pass = check_tests_pass(pred.patch_diff)
return (builds * 0.3 + fixes_pov * 0.5 + tests_pass * 0.2)
# Optimize the pipeline
optimizer = MIPROv2(
metric=patch_success_metric,
num_candidates=20, # Generate 20 prompt variations
init_temperature=1.0
)
optimized_patcher = optimizer.compile(
PatchGenerator(),
trainset=train_data,
valset=val_data,
num_trials=50, # Run 50 optimization trials
max_bootstrapped_demos=3,
max_labeled_demos=5
)
Implementation Plan
Phase 1: Foundation (Week 1-2)
-
Set up DSPy infrastructure
- Add DSPy to patcher dependencies
- Create DSPy configuration module
- Set up telemetry for optimization metrics
-
Create base signatures
- Define signatures for each agent's core functionality
- Implement type-safe input/output fields
- Add descriptions for all fields
-
Build evaluation framework
- Implement success metrics (build, PoV, tests)
- Create training data loader from historical patches
- Set up validation pipeline
Phase 2: Core Module Migration (Week 3-4)
-
Migrate RootCauseAgent
- Convert hardcoded prompt to
RootCauseSignature - Implement
dspy.ChainOfThoughtfor analysis - Add tool integration for code understanding
- Convert hardcoded prompt to
-
Migrate SWEAgent
- Split into
PatchStrategyModuleandPatchCreationModule - Chain modules using DSPy composition
- Preserve existing tool usage patterns
- Split into
-
Migrate ReflectionAgent
- Convert 12-step process to
dspy.ReActagent - Integrate existing analysis tools
- Add adaptive decision making
- Convert 12-step process to
Phase 3: Advanced Features (Week 5-6)
-
Implement ContextRetriever as DSPy Module
- Create retrieval signatures for different context types
- Use
dspy.Retrievefor semantic search - Optimize context selection
-
Build ensemble capabilities
- Create
dspy.Ensembleof top-performing modules - Implement voting mechanisms for patch selection
- Add confidence scoring
- Create
-
Add optimization pipeline
- Implement MIPROv2 optimization
- Create automated retraining pipeline
- Set up A/B testing framework
Phase 4: Integration & Testing (Week 7-8)
-
LangGraph integration
- Wrap DSPy modules as LangGraph nodes
- Preserve existing state management
- Maintain backward compatibility
-
Comprehensive testing
- Unit tests for each DSPy module
- Integration tests with full pipeline
- Performance benchmarking vs current system
-
Gradual rollout
- Feature flag for DSPy vs legacy mode
- Shadow mode operation for comparison
- Monitoring and alerting setup
Success Metrics
Primary Metrics
- Patch Success Rate: Target 20% improvement over baseline
- Mean Time to Patch: Reduce by 30% through optimized prompts
- PoV Fix Rate: Increase from current baseline by 25%
Secondary Metrics
- LLM Token Usage: Reduce by 40% through optimized prompts
- Parsing Failure Rate: Reduce from current to <1%
- Agent Retry Count: Decrease average retries by 50%
Quality Metrics
- Code Quality: Maintain or improve linting/formatting compliance
- Test Coverage: Ensure patches don't reduce test coverage
- Security: Validate patches don't introduce new vulnerabilities
Technical Requirements
Dependencies
[tool.poetry.dependencies]
dspy-ai = "^2.6.0" # Latest DSPy version
pydantic = "^2.0" # For structured outputs
mlflow = "^2.0" # For experiment tracking
Infrastructure
- GPU Access: Optional but recommended for optimization phase
- Storage: ~10GB for training data and optimized models
- Compute: MIPROv2 optimization requires ~20 hours of LLM calls
Backward Compatibility
- Maintain LangGraph state interface
- Support existing Redis queue integration
- Preserve current monitoring/telemetry
Risk Mitigation
Technical Risks
-
DSPy Learning Curve
- Mitigation: Start with simple modules, gradually increase complexity
- Provide team training on DSPy concepts
-
Integration Complexity
- Mitigation: Maintain parallel implementations initially
- Extensive testing before full migration
-
Performance Regression
- Mitigation: Shadow mode operation for comparison
- Rollback capability via feature flags
Operational Risks
-
Optimization Cost
- Mitigation: Use cheaper models for initial optimization
- Implement caching for repeated evaluations
-
Model Drift
- Mitigation: Regular reoptimization schedule
- Monitoring for performance degradation
Resources & References
DSPy Documentation
Relevant Papers
- "DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines" (2023)
- "Optimizing Instructions and Demonstrations for Multi-Stage Language Model Programs" (2024)
Internal Resources
- Current patcher codebase:
/patcher/src/buttercup/patcher/ - Historical patch data: Available in Redis/telemetry
- Team expertise: LangGraph, LLM integration experience
Expected Outcomes
By migrating to DSPy, we expect to:
- Eliminate manual prompt engineering - Replace 500+ lines of hardcoded prompts with optimizable signatures
- Improve reliability - Reduce parsing failures and increase success rates through structured outputs
- Enable continuous improvement - Automatic optimization based on real-world performance data
- Accelerate development - Faster iteration on agent capabilities without manual prompt tuning
- Reduce operational costs - More efficient token usage through optimized prompts
Next Steps
- Team Review: Schedule architecture review meeting
- Proof of Concept: Build DSPy version of RootCauseAgent
- Benchmark: Compare PoC performance against current implementation
- Resource Allocation: Assign team members and timeline
- Kickoff: Begin Phase 1 implementation
This migration represents a significant architectural improvement that will make our patcher more reliable, maintainable, and effective at automatically fixing security vulnerabilities. The investment in DSPy will pay dividends through improved success rates and reduced maintenance burden.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the current agent implementation in /patcher/src/buttercup/patcher/, especially rootcause.py, reflection.py, swe.py, and common.py, along with the LangGraph orchestration. Review the proposed DSPy signatures, optimization metrics, dependency changes, and phased migration plan before estimating work. Done would require a coordinated migration, integration and regression testing, optimization, and rollout rather than a single localized change.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- ai, backend
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 15/100