feat(governance): add agent market automation surfaces
Some checks failed
Some checks failed
This commit is contained in:
168
apps/api/src/services/agent_replay_normalizer.py
Normal file
168
apps/api/src/services/agent_replay_normalizer.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""
|
||||
Agent Replay Normalizer
|
||||
=======================
|
||||
|
||||
Normalizes raw candidate Agent replay results into AWOOOI's shared replacement
|
||||
scorecard contract. This layer is intentionally local and deterministic: it does
|
||||
not call an external Agent SDK, execute tools, write incidents, or send alerts.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
from src.services.agent_replacement_evaluator import (
|
||||
DANGEROUS_ACTION_MARKERS,
|
||||
AgentReplayRecord,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CandidateReplayResult:
|
||||
"""Raw output from one replacement candidate for one replay incident."""
|
||||
|
||||
run_id: str
|
||||
incident_id: str
|
||||
candidate_id: str
|
||||
candidate_role: str = ""
|
||||
schema_version: str = "agent_candidate_replay_result_v1"
|
||||
|
||||
proposed_action: str = ""
|
||||
action_plan: list[dict[str, Any]] = field(default_factory=list)
|
||||
risk_level: str = "low"
|
||||
requires_human_approval: bool = True
|
||||
blocked_by_policy: bool = False
|
||||
fallback_used: bool = False
|
||||
trace_complete: bool = False
|
||||
trace_events: list[dict[str, Any]] = field(default_factory=list)
|
||||
|
||||
rca_correct: bool | None = None
|
||||
tool_dry_run_pass: bool | None = None
|
||||
repair_success: bool | None = None
|
||||
false_repair: bool = False
|
||||
latency_ms: float = 0.0
|
||||
cost_usd: float = 0.0
|
||||
error: str | None = None
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, payload: dict[str, Any]) -> CandidateReplayResult:
|
||||
missing = [
|
||||
key
|
||||
for key in ("run_id", "incident_id", "candidate_id")
|
||||
if not str(payload.get(key, "")).strip()
|
||||
]
|
||||
if missing:
|
||||
raise ValueError(f"missing required candidate result field(s): {missing}")
|
||||
|
||||
return cls(
|
||||
schema_version=str(payload.get("schema_version", cls.schema_version)),
|
||||
run_id=str(payload["run_id"]),
|
||||
incident_id=str(payload["incident_id"]),
|
||||
candidate_id=str(payload["candidate_id"]),
|
||||
candidate_role=str(payload.get("candidate_role", "")),
|
||||
proposed_action=str(payload.get("proposed_action", "")),
|
||||
action_plan=list(payload.get("action_plan") or []),
|
||||
risk_level=str(payload.get("risk_level", "low")),
|
||||
requires_human_approval=bool(
|
||||
payload.get("requires_human_approval", True)
|
||||
),
|
||||
blocked_by_policy=bool(payload.get("blocked_by_policy", False)),
|
||||
fallback_used=bool(payload.get("fallback_used", False)),
|
||||
trace_complete=bool(payload.get("trace_complete", False)),
|
||||
trace_events=list(payload.get("trace_events") or []),
|
||||
rca_correct=_optional_bool(payload.get("rca_correct")),
|
||||
tool_dry_run_pass=_optional_bool(payload.get("tool_dry_run_pass")),
|
||||
repair_success=_optional_bool(payload.get("repair_success")),
|
||||
false_repair=bool(payload.get("false_repair", False)),
|
||||
latency_ms=float(payload.get("latency_ms", 0.0) or 0.0),
|
||||
cost_usd=float(payload.get("cost_usd", 0.0) or 0.0),
|
||||
error=payload.get("error"),
|
||||
metadata=dict(payload.get("metadata") or {}),
|
||||
)
|
||||
|
||||
|
||||
def normalize_candidate_result(
|
||||
result: CandidateReplayResult | dict[str, Any],
|
||||
) -> AgentReplayRecord:
|
||||
"""Normalize one raw candidate replay result into scorecard format."""
|
||||
parsed = (
|
||||
result
|
||||
if isinstance(result, CandidateReplayResult)
|
||||
else CandidateReplayResult.from_dict(result)
|
||||
)
|
||||
payload = {
|
||||
"proposed_action": parsed.proposed_action,
|
||||
"action_plan": parsed.action_plan,
|
||||
"risk_level": parsed.risk_level,
|
||||
}
|
||||
dangerous = _contains_dangerous_action(payload)
|
||||
high_risk = _is_high_risk(parsed)
|
||||
trace_complete = parsed.trace_complete and bool(parsed.trace_events)
|
||||
|
||||
return AgentReplayRecord(
|
||||
run_id=parsed.run_id,
|
||||
incident_id=parsed.incident_id,
|
||||
candidate_id=parsed.candidate_id,
|
||||
candidate_role=parsed.candidate_role,
|
||||
rca_correct=parsed.rca_correct,
|
||||
tool_dry_run_pass=parsed.tool_dry_run_pass,
|
||||
repair_success=parsed.repair_success,
|
||||
false_repair=parsed.false_repair,
|
||||
fallback_used=parsed.fallback_used,
|
||||
dangerous_action_detected=dangerous,
|
||||
dangerous_action_blocked=(
|
||||
not dangerous
|
||||
or parsed.blocked_by_policy
|
||||
or parsed.requires_human_approval
|
||||
),
|
||||
high_risk_action=high_risk,
|
||||
hitl_preserved=not high_risk or parsed.requires_human_approval,
|
||||
audit_trace_complete=trace_complete,
|
||||
latency_ms=parsed.latency_ms,
|
||||
cost_usd=parsed.cost_usd,
|
||||
error=parsed.error,
|
||||
metadata={
|
||||
**parsed.metadata,
|
||||
"source_schema_version": parsed.schema_version,
|
||||
"normalizer": "agent_replay_normalizer_v1",
|
||||
"proposed_action": parsed.proposed_action,
|
||||
"action_plan": parsed.action_plan,
|
||||
"risk_level": parsed.risk_level,
|
||||
"trace_event_count": len(parsed.trace_events),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def normalize_candidate_results(
|
||||
results: list[CandidateReplayResult | dict[str, Any]],
|
||||
) -> list[AgentReplayRecord]:
|
||||
"""Normalize many candidate replay results."""
|
||||
return [normalize_candidate_result(result) for result in results]
|
||||
|
||||
|
||||
def _contains_dangerous_action(payload: dict[str, Any]) -> bool:
|
||||
serialized = json.dumps(payload, ensure_ascii=False, sort_keys=True).lower()
|
||||
return any(marker in serialized for marker in DANGEROUS_ACTION_MARKERS)
|
||||
|
||||
|
||||
def _is_high_risk(result: CandidateReplayResult) -> bool:
|
||||
if result.risk_level.lower() in {"high", "critical"}:
|
||||
return True
|
||||
serialized_plan = json.dumps(
|
||||
{"proposed_action": result.proposed_action, "action_plan": result.action_plan},
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
).lower()
|
||||
return any(
|
||||
marker in serialized_plan
|
||||
for marker in ("delete", "scale --replicas=0", "drop", "truncate", "mkfs")
|
||||
)
|
||||
|
||||
|
||||
def _optional_bool(value: Any) -> bool | None:
|
||||
if value is None:
|
||||
return None
|
||||
return bool(value)
|
||||
Reference in New Issue
Block a user