Make agent validation self-correcting

This commit is contained in:
2026-08-27 19:07:40 +02:00
parent 66b6df03a6
commit 4895a2a91b
9 changed files with 114 additions and 16 deletions

View File

@@ -43,6 +43,7 @@ class Settings:
poll_seconds: int
max_changed_files: int
max_changed_lines: int
max_agent_attempts: int
stale_claim_minutes: int
dry_run: bool
@@ -75,7 +76,7 @@ class Settings:
test_commands=_commands(
os.getenv(
"TEST_COMMANDS",
"composer install --no-interaction --prefer-dist;;php artisan migrate:fresh --force;;php artisan test",
"composer install --no-interaction --prefer-dist;;npm ci;;npm run build;;php artisan migrate:fresh --force;;php artisan test",
)
),
test_database_host=os.getenv("TEST_DB_HOST", "host.docker.internal"),
@@ -88,6 +89,7 @@ class Settings:
poll_seconds=int(os.getenv("POLL_SECONDS", "60")),
max_changed_files=int(os.getenv("MAX_CHANGED_FILES", "12")),
max_changed_lines=int(os.getenv("MAX_CHANGED_LINES", "800")),
max_agent_attempts=int(os.getenv("MAX_AGENT_ATTEMPTS", "2")),
stale_claim_minutes=int(os.getenv("STALE_CLAIM_MINUTES", "90")),
dry_run=mode == "dry-run",
)
@@ -103,3 +105,5 @@ class Settings:
raise ValueError("FAILURE_DB_PASSWORD is required")
if not self.test_database_password:
raise ValueError("TEST_DB_PASSWORD is required")
if not 1 <= self.max_agent_attempts <= 3:
raise ValueError("MAX_AGENT_ATTEMPTS must be between 1 and 3")

View File

@@ -71,3 +71,17 @@ Generated SQL, if relevant:
Run focused tests while working. Leave the repository with the fix and regression test applied.
"""
def build_retry_task(exception: Exception) -> str:
failure = redact(str(exception))[-6_000:]
return f"""The previous implementation failed isolated validation.
Correct the existing changes using this test output:
{failure}
Keep the regression test meaningful. Do not remove or weaken tests, safety checks,
authentication, authorization, SQL validation, or tenant isolation. Do not commit or push.
"""

View File

@@ -12,7 +12,7 @@ from .git import GitWorkspace
from .gitea import GiteaClient
from .models import FailureReport
from .policy import evaluate_changes
from .prompt import build_task
from .prompt import build_retry_task, build_task
LOG = logging.getLogger(__name__)
@@ -43,15 +43,29 @@ class AutomationWorker:
try:
workspace.prepare()
task = build_task(report)
metadata["agent_output"] = self.agent.run(workspace.path, task)
metadata["agent_output"] = []
for attempt in range(1, self.settings.max_agent_attempts + 1):
metadata["agent_output"].append(self.agent.run(workspace.path, task))
summary = workspace.summary()
metadata["changes"] = asdict(summary)
policy = evaluate_changes(summary, self.settings, workspace.path)
if not policy.allowed:
raise RuntimeError(
"Policy rejected changes: " + "; ".join(policy.errors)
)
try:
metadata["tests"] = self._run_tests(workspace)
break
except RuntimeError as exception:
if attempt >= self.settings.max_agent_attempts:
raise
LOG.warning(
"Tests failed for report %s on attempt %s; requesting correction",
report.id,
attempt,
)
task = build_retry_task(exception)
summary = workspace.summary()
metadata["changes"] = asdict(summary)
policy = evaluate_changes(summary, self.settings, workspace.path)
if not policy.allowed:
raise RuntimeError("Policy rejected changes: " + "; ".join(policy.errors))
metadata["tests"] = self._run_tests(workspace)
summary = workspace.summary()
metadata["changes"] = asdict(summary)
policy = evaluate_changes(summary, self.settings, workspace.path)
@@ -101,6 +115,9 @@ class AutomationWorker:
results = []
test_environment = {
"AGENT_TEST_DB_ENABLED": "true",
"APP_ENV": "testing",
"APP_KEY": "base64:MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=",
"AI_MISTRAL_API_KEY": "automation-test-key",
"TEST_DB_HOST": self.settings.test_database_host,
"TEST_DB_PORT": str(self.settings.test_database_port),
"TEST_DB_NAME": self.settings.test_database_name,