Patch Stage
PatchStage produces minimal, validated patches for verified bugs. It runs
after VerifyStage in the verify_and_patch workflow. For each verified bug it
generates a candidate diff, reviews its minimality, applies it in isolation,
rechecks that the vulnerability is gone, and runs integration tests.
Location
src/stages/patch/stage.py
Stage declaration
class PatchStage(BaseStage[StageInput, StageOutput]):
name = StageName.PATCH
required_artifacts = ("source_snapshot", "verification_evidence")
produced_artifacts = ("patch",)
Per-finding pipeline
Each bug is processed through up to 3 attempts by default (configurable via
patch_config["max_attempts"]). Bugs with verification.status != "verified"
are skipped immediately.
Step 1 — Gather context
- Load the original finding from
input.data["bugs"]. - Load the matching verification result from the
verification_evidenceartifact. - Extract
root_cause,evidence,bug_description, source file paths, and sink/source descriptions. - Obtain plugin patch policy:
context.plugins.plugin.patch_policy(context, bug).
Step 2 — Generate minimal candidate
Runs an agent task with purpose="patch.generate" using
stages/patch/prompts/generate.txt.
The agent must:
- Address only the verified root cause.
- Prefer existing helpers and local code style.
- Avoid broad refactors, unrelated cleanups, speculative hardening.
- Return a unified diff.
- Return changed-line count and touched files/functions.
- Explain why each touched location is necessary.
Changed-line count = added + removed code lines (excluding file headers, hunk headers, and context lines).
Step 3 — Minimality review gate
If the candidate changes more than 10 code lines, runs a separate agent task
with purpose="patch.minimality_review" using
stages/patch/prompts/minimality_review.txt.
The review must answer:
- Are all changed lines necessary to eliminate the verified root cause?
- Can any lines be removed without reintroducing the vulnerability?
- Is the patch doing unrelated refactoring or behavior changes?
If the review finds the candidate unsafe, the stage either retries generation with feedback or rejects the candidate.
Step 4 — Apply in isolation
Applies the unified diff to an isolated copy of the source snapshot. The original checkout is never mutated.
Records:
status:"applied"or"failed"error: patch apply error if anyapplied_paths: files changed by the patch
If the patch does not apply cleanly, generation retries with the apply error as feedback.
Step 5 — Vulnerability recheck
Runs an agent task with purpose="patch.vulnerability_recheck" using
stages/patch/prompts/vulnerability_recheck.txt.
The agent tries to find the same vulnerability again in the patched source, checking the previous vulnerable files/functions first and nearby call paths if needed.
Interpretation:
- If the vulnerability is still reachable → candidate fails.
- If the agent explains why the original path is blocked → candidate passes.
- If uncertain → candidate rejected or flagged low confidence.
Step 6 — Integration test
Runs focused tests after the vulnerability recheck passes.
Test source priority:
- Backend-provided
options["patch_test_commands"]in request metadata. - Plugin test policy (
patch_policyhook). - Tests identified by the agent from changed files.
- Manual workflow description when no executable test is available.
Records status: "passed", "failed", or "not_run".
Step 7 — Accept or reject
A candidate is accepted when:
- Patch applies cleanly.
- Minimality gate passes (or was not required).
- Vulnerability recheck cannot find the original vulnerability.
- Integration test passes, or no executable test exists and the absence is recorded.
A candidate is rejected when:
- Patch cannot be applied.
- Minimality review cannot justify the changes.
- Original vulnerability is still present after patch.
- Integration test shows a regression.
- Agent cannot produce a patch with enough confidence.
vega.remediation_results artifact
PatchStage writes format_version 2:
{
"format_version": 2,
"artifact_kind": "vega.remediation_results",
"repo_id": "...",
"run_id": "...",
"bug_count": 1,
"eligible_bug_count": 1,
"accepted_patch_count": 1,
"rejected_patch_count": 0,
"results": [
{
"finding_id": "bug_1",
"status": "accepted",
"root_cause": "...",
"fix_strategy": "add existing authorization guard",
"proposed_fixes": [
{
"title": "...",
"patch": "--- a/src/service.py\n+++ ...",
"changed_line_count": 6,
"affected_paths": ["src/service.py"],
"affected_symbols": ["handle_admin_action"]
}
],
"minimality_review": {
"required": false,
"status": "not_required",
"changed_line_count": 6
},
"apply_result": { "status": "applied", "applied_paths": ["src/service.py"] },
"vulnerability_recheck": {
"status": "passed",
"vulnerability_still_present": false
},
"integration_test": { "status": "passed", "commands": ["pytest tests/"] },
"attempts": 1,
"confidence": "high"
}
],
"summary": {
"patch_count": 1,
"skipped_unverified_count": 0,
"minimality_reviewed_count": 0,
"vulnerability_recheck_passed_count": 1,
"integration_test_passed_count": 1
}
}
Result status values:
| Status | Meaning |
|---|---|
"accepted" |
All gates passed |
"rejected" |
Candidate failed; attempts exhausted |
"skipped_unverified" |
Bug was not verified |
"no_safe_patch" |
Root cause is valid but no safe minimal fix found |
Domain events
| Event kind | Payload |
|---|---|
patch_candidate_created |
finding_id, attempt, diff summary |
patch_minimality_reviewed |
finding_id, review decision |
patch_apply_completed |
finding_id, status, applied_paths |
patch_vulnerability_rechecked |
finding_id, status, vulnerability_still_present |
patch_integration_test_completed |
finding_id, status, test output |
patch_finding_completed |
finding_id, final status |
patch_created |
Final artifact ref + summary counts |
Plugin hook
PatchStage calls context.plugins.plugin.patch_policy(context, bug) for each
bug to get plugin-specific patch policy (e.g. test commands, patch constraints,
domain-specific code style rules for the fix).
Cancellation
PatchStage calls context.checkpoint(StageName.PATCH, progress) between
finding iterations. Cancellation terminates in-flight agent calls.
Prompts
| File | Purpose |
|---|---|
stages/patch/prompts/generate.txt |
Candidate diff generation |
stages/patch/prompts/minimality_review.txt |
Review for over-10-line candidates |
stages/patch/prompts/vulnerability_recheck.txt |
Search for original vuln in patched source |
stages/patch/prompts/default.txt |
Fallback / combined prompt |