Route auto review requests to agentic review

This commit is contained in:
2026-05-15 15:53:52 +02:00
parent 9fa1d9e9b5
commit 8f484cd855
15 changed files with 208 additions and 47 deletions

View File

@@ -1,3 +1,4 @@
from repo_registry.acceptance.agentic import AgenticReviewer, AgenticReviewRequest
from repo_registry.acceptance.criteria import (
active_quality_criteria_version,
criteria_registry_dict,
@@ -14,6 +15,8 @@ from repo_registry.acceptance.gates import (
__all__ = [
"active_quality_criteria_version",
"AgenticReviewer",
"AgenticReviewRequest",
"blocking_quality_gate_outcomes",
"criteria_registry_dict",
"criteria_registry_json",

View File

@@ -0,0 +1,24 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Protocol
from repo_registry.acceptance.gates import QualityGateOutcome
from repo_registry.core.models import CandidateGraph, Repository
@dataclass(frozen=True)
class AgenticReviewRequest:
repository: Repository
candidate_graph: CandidateGraph
criteria_version: str
quality_gate_outcomes: list[QualityGateOutcome]
context: str
class AgenticReviewer(Protocol):
reviewer_id: str
policy_version: str
def review(self, request: AgenticReviewRequest) -> None:
"""Review a candidate graph and record decisions through the caller."""

View File

@@ -40,9 +40,9 @@ def build_parser() -> argparse.ArgumentParser:
rebuild.add_argument("--dry-run", action="store_true", help="Preview without clearing approved characteristics.")
rebuild.add_argument("--no-llm", action="store_true", help="Disable configured LLM assistance.")
rebuild.add_argument(
"--trusted-auto-approve",
"--agentic-review",
action="store_true",
help="Run trusted auto-approval after a confirmed rebuild.",
help="Request configured agentic review after a confirmed rebuild.",
)
rebuild.add_argument(
"--confirm",
@@ -146,7 +146,7 @@ def build_parser() -> argparse.ArgumentParser:
self_assess.add_argument(
"--agentic-review",
action="store_true",
help="Reserved for a configured agentic reviewer; currently errors when requested.",
help="Request configured agentic review; leaves candidates pending when none is configured.",
)
self_assess.add_argument(
"--fail-on-regression",
@@ -213,11 +213,11 @@ def rebuild_characteristics_command(
confirm=not dry_run,
use_llm_assistance=not args.no_llm,
)
if args.trusted_auto_approve and not dry_run and result.analysis_run.status == "completed":
service.trusted_auto_approve_candidate_graph(
if args.agentic_review and not dry_run and result.analysis_run.status == "completed":
service.request_agentic_review(
repository.id,
result.analysis_run.id,
notes="CLI trusted auto-approve after rebuild.",
notes="CLI agentic review request after rebuild.",
)
print(rebuild_summary_line(service, result, args))
return 0
@@ -258,8 +258,6 @@ def self_assess_command(
args: argparse.Namespace,
parser: argparse.ArgumentParser,
) -> int:
if args.agentic_review:
parser.error("agentic review is not configured yet")
service = service_from_args(args)
source_path = Path(args.source_path).expanduser().resolve()
if not source_path.is_dir():
@@ -269,6 +267,7 @@ def self_assess_command(
repository.id,
source_path=str(source_path),
use_llm_assistance=not args.no_llm,
agentic_review=args.agentic_review,
trusted_auto_approve=False,
)
if summary.analysis_run.status != "completed":

View File

@@ -5,8 +5,12 @@ from dataclasses import asdict, replace
from typing import Any
from repo_registry.acceptance import (
AgenticReviewer,
AgenticReviewRequest,
active_quality_criteria_version,
blocking_quality_gate_outcomes,
evaluate_candidate_capability_quality,
evaluate_candidate_graph_quality,
)
from repo_registry.core.models import (
AbilitySummary,
@@ -57,6 +61,7 @@ class RegistryService:
ingestion: GitIngestionService | None = None,
llm_extractor: LLMCandidateExtractor | None = None,
embedding_provider: EmbeddingProvider | None = None,
agentic_reviewer: AgenticReviewer | None = None,
) -> None:
self.store = store
self.scanner = DeterministicScanner()
@@ -67,6 +72,7 @@ class RegistryService:
self.llm_extractor = llm_extractor
self.llm_mapper = LLMExtractionMapper()
self.embedding_provider = embedding_provider
self.agentic_reviewer = agentic_reviewer
def register_repository(
self,
@@ -135,6 +141,7 @@ class RegistryService:
source_path: str | None = None,
use_cached_checkout: bool = False,
use_llm_assistance: bool = True,
agentic_review: bool = False,
trusted_auto_approve: bool = False,
access_username: str | None = None,
access_password: str | None = None,
@@ -241,13 +248,20 @@ class RegistryService:
f"from {candidate_source} candidate generation."
),
)
if trusted_auto_approve:
self.trusted_auto_approve_candidate_graph(
if agentic_review or trusted_auto_approve:
legacy_note = (
" Deprecated trusted_auto_approve request was routed to "
"agentic review."
if trusted_auto_approve
else ""
)
self.request_agentic_review(
repository_id,
completed_run.id,
notes=(
"Trusted auto-populate mode reviewed candidate graph "
f"after {candidate_source} candidate generation."
"Agentic review requested after "
f"{candidate_source} candidate generation."
f"{legacy_note}"
),
)
log_operation(
@@ -602,6 +616,50 @@ class RegistryService:
)
return self.store.get_ability_map(repository_id)
def request_agentic_review(
self,
repository_id: int,
analysis_run_id: int,
*,
notes: str = "",
) -> CandidateGraph:
graph = self.store.get_candidate_graph(repository_id, analysis_run_id)
gate_outcomes = evaluate_candidate_graph_quality(graph)
criteria_version = active_quality_criteria_version()
if self.agentic_reviewer is None:
self.store.create_review_decision(
repository_id,
analysis_run_id,
action="agentic_review_unconfigured",
notes=(
f"{notes} No agentic reviewer is configured; candidates "
"remain pending human review. "
f"criteria_version={criteria_version}; "
f"quality_gate_outcomes={len(gate_outcomes)}."
).strip(),
)
return graph
request = AgenticReviewRequest(
repository=graph.repository,
candidate_graph=graph,
criteria_version=criteria_version,
quality_gate_outcomes=gate_outcomes,
context="candidate-characteristic-acceptance",
)
self.agentic_reviewer.review(request)
self.store.create_review_decision(
repository_id,
analysis_run_id,
action="agentic_review_completed",
notes=(
f"{notes} reviewer={self.agentic_reviewer.reviewer_id}; "
f"policy_version={self.agentic_reviewer.policy_version}; "
f"criteria_version={criteria_version}; "
f"quality_gate_outcomes={len(gate_outcomes)}."
).strip(),
)
return self.store.get_candidate_graph(repository_id, analysis_run_id)
def _trusted_auto_approve_capability_safe(
self,
capability: CandidateCapability,

View File

@@ -423,6 +423,8 @@ def _known_regression_patterns(
def _execution_mode(decisions: list[ReviewDecision]) -> str:
if any(decision.action.startswith("agentic_review") for decision in decisions):
return "agentic-review"
if any(decision.action == "trusted_auto_approve_candidate_graph" for decision in decisions):
return "trusted-auto-review"
if any(decision.action == "llm_extraction_used" for decision in decisions):
@@ -439,6 +441,12 @@ def _candidate_source(decisions: list[ReviewDecision]) -> str:
def _acceptance_mode(decisions: list[ReviewDecision]) -> str:
agentic_decision = next(
(decision for decision in decisions if decision.action.startswith("agentic_review")),
None,
)
if agentic_decision is not None:
return agentic_decision.action
if any(decision.action == "trusted_auto_approve_candidate_graph" for decision in decisions):
return "trusted_auto_approve_candidate_graph"
if any(decision.action == "approve_candidate_graph" for decision in decisions):

View File

@@ -295,6 +295,7 @@ def create_analysis_run(
source_path=payload.source_path,
use_cached_checkout=payload.use_cached_checkout,
use_llm_assistance=payload.use_llm_assistance,
agentic_review=payload.agentic_review,
trusted_auto_approve=payload.trusted_auto_approve,
access_username=payload.access_username,
access_password=payload.access_password,

View File

@@ -214,7 +214,20 @@ class AnalysisRunCreate(BaseModel):
source_path: str | None = None
use_cached_checkout: bool = False
use_llm_assistance: bool = True
trusted_auto_approve: bool = False
agentic_review: bool = Field(
default=False,
description=(
"Request configured agentic review after analysis; candidates remain "
"pending when no reviewer is configured."
),
)
trusted_auto_approve: bool = Field(
default=False,
description=(
"Deprecated compatibility input. Requests are routed to agentic "
"review and do not deterministically approve candidates."
),
)
access_username: str | None = None
access_password: str | None = Field(default=None, repr=False)
@@ -225,7 +238,7 @@ class AnalysisRunCreate(BaseModel):
{"source_path": "/path/to/local/repository"},
{"use_cached_checkout": True},
{"use_llm_assistance": False},
{"trusted_auto_approve": True},
{"agentic_review": True},
{
"access_username": "git-user",
"access_password": "access-token",

View File

@@ -416,7 +416,7 @@ def render_repository_index(
<label>Password or access token <input name="access_password" type="password" autocomplete="current-password" placeholder="Used for this Git operation only"></label>
<label class="checkbox"><input type="checkbox" name="explore_after_registration" value="1" checked> Explore after registration</label>
<label class="checkbox"><input type="checkbox" name="use_llm_assistance" value="1" checked> Use LLM assistance if configured</label>
<label class="checkbox"><input type="checkbox" name="trusted_auto_approve" value="1"> Trusted auto-populate after analysis</label>
<label class="checkbox"><input type="checkbox" name="agentic_review" value="1"> Request agentic review after analysis</label>
<div class="actions">
<button type="submit">Register</button>
<span data-pending>Registering repository...</span>
@@ -1440,7 +1440,7 @@ def create_repository_from_form(
access_password: str = Form(""),
explore_after_registration: str | None = Form(None),
use_llm_assistance: str | None = Form(None),
trusted_auto_approve: str | None = Form(None),
agentic_review: str | None = Form(None),
service: RegistryService = Depends(get_service),
):
try:
@@ -1460,7 +1460,7 @@ def create_repository_from_form(
summary = service.analyze_repository(
repository.id,
use_llm_assistance=bool(use_llm_assistance),
trusted_auto_approve=bool(trusted_auto_approve),
agentic_review=bool(agentic_review),
access_username=access_username or None,
access_password=access_password or None,
)
@@ -1529,7 +1529,7 @@ def repository_detail(
<label>Override source path <input name="source_path" placeholder="Optional local path"></label>
<label class="checkbox"><input type="checkbox" name="use_cached_checkout" value="1"> Analyze cached checkout without fetching upstream</label>
<label class="checkbox"><input type="checkbox" name="use_llm_assistance" value="1" checked> Use LLM assistance if configured</label>
<label class="checkbox"><input type="checkbox" name="trusted_auto_approve" value="1"> Trusted auto-populate after analysis</label>
<label class="checkbox"><input type="checkbox" name="agentic_review" value="1"> Request agentic review after analysis</label>
<label>Username <input name="access_username" autocomplete="username" placeholder="Optional for private HTTP(S) repos"></label>
<label>Password or access token <input name="access_password" type="password" autocomplete="current-password" placeholder="Used for this Git operation only"></label>
<div class="actions">
@@ -1964,7 +1964,7 @@ def create_analysis_run_from_form(
source_path: str = Form(""),
use_cached_checkout: str | None = Form(None),
use_llm_assistance: str | None = Form(None),
trusted_auto_approve: str | None = Form(None),
agentic_review: str | None = Form(None),
access_username: str = Form(""),
access_password: str = Form(""),
service: RegistryService = Depends(get_service),
@@ -1974,7 +1974,7 @@ def create_analysis_run_from_form(
source_path=source_path or None,
use_cached_checkout=bool(use_cached_checkout),
use_llm_assistance=bool(use_llm_assistance),
trusted_auto_approve=bool(trusted_auto_approve),
agentic_review=bool(agentic_review),
access_username=access_username or None,
access_password=access_password or None,
)