Phase 8 Production Hardening with complete governance infrastructure: - Vault integration with tiered policies (T0-T4) - DragonflyDB state management - SQLite audit ledger - Pipeline DSL and templates - Promotion/revocation engine - Checkpoint system for session persistence - Health manager and circuit breaker for fault tolerance - GitHub/Slack integrations - Architectural test pipeline with bug watcher, suggestion engine, council review - Multi-agent chaos testing framework Test Results: - Governance tests: 68/68 passing - E2E workflow: 16/16 passing - Phase 2 Vault: 14/14 passing - Integration tests: 27/27 passing Coverage: 57.6% average across 12 phases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
649 lines
24 KiB
Python
649 lines
24 KiB
Python
"""
|
||
Council/Review Layer
|
||
====================
|
||
Multi-agent review system that aggregates knowledge, debates suggestions,
|
||
and makes decisions on fixes. Tracks outcomes for future learning.
|
||
|
||
Features:
|
||
- Aggregates past knowledge and quality metrics
|
||
- Multi-perspective review (safety, performance, architecture)
|
||
- Decision tracking with outcomes
|
||
- Learning from past decisions
|
||
"""
|
||
|
||
import json
|
||
import hashlib
|
||
from datetime import datetime, timezone
|
||
from dataclasses import dataclass, field, asdict
|
||
from enum import Enum
|
||
from pathlib import Path
|
||
from typing import Any, Optional
|
||
import redis
|
||
|
||
from .suggestion_engine import Suggestion, SuggestionStatus, RiskLevel, ImpactLevel
|
||
|
||
|
||
class ReviewerRole(str, Enum):
|
||
"""Roles of council reviewers"""
|
||
SAFETY = "safety" # Security and stability
|
||
PERFORMANCE = "performance" # Efficiency and speed
|
||
ARCHITECTURE = "architecture" # Design and maintainability
|
||
COMPLIANCE = "compliance" # Policy and governance
|
||
QUALITY = "quality" # Code quality and testing
|
||
|
||
|
||
class VoteType(str, Enum):
|
||
"""Types of votes"""
|
||
APPROVE = "approve"
|
||
REJECT = "reject"
|
||
ABSTAIN = "abstain"
|
||
NEEDS_MORE_INFO = "needs_more_info"
|
||
|
||
|
||
class DecisionType(str, Enum):
|
||
"""Final decision types"""
|
||
AUTO_APPROVE = "auto_approve" # Safe to auto-implement
|
||
HUMAN_APPROVE = "human_approve" # Approved, needs human to implement
|
||
DEFER = "defer" # Needs more discussion
|
||
REJECT = "reject" # Do not implement
|
||
ESCALATE = "escalate" # Needs higher authority
|
||
|
||
|
||
@dataclass
|
||
class Vote:
|
||
"""A single vote from a reviewer"""
|
||
reviewer_role: ReviewerRole
|
||
vote: VoteType
|
||
reasoning: str
|
||
concerns: list[str] = field(default_factory=list)
|
||
conditions: list[str] = field(default_factory=list) # "Approve if..."
|
||
confidence: float = 0.8 # 0-1
|
||
timestamp: str = ""
|
||
|
||
def __post_init__(self):
|
||
if not self.timestamp:
|
||
self.timestamp = datetime.now(timezone.utc).isoformat()
|
||
|
||
|
||
@dataclass
|
||
class Decision:
|
||
"""Council decision on a suggestion"""
|
||
id: str
|
||
suggestion_id: str
|
||
decision: DecisionType
|
||
votes: list[Vote]
|
||
final_reasoning: str
|
||
conditions: list[str] = field(default_factory=list)
|
||
requires_manual_oversight: bool = True
|
||
auto_fix_approved: bool = False
|
||
decided_at: str = ""
|
||
implemented_at: Optional[str] = None
|
||
outcome: Optional[str] = None # "success", "failure", "partial"
|
||
outcome_notes: Optional[str] = None
|
||
lessons_learned: Optional[str] = None
|
||
|
||
def __post_init__(self):
|
||
if not self.decided_at:
|
||
self.decided_at = datetime.now(timezone.utc).isoformat()
|
||
if not self.id:
|
||
self.id = f"dec-{hashlib.sha256(f'{self.suggestion_id}{self.decided_at}'.encode()).hexdigest()[:12]}"
|
||
|
||
|
||
@dataclass
|
||
class ReviewerProfile:
|
||
"""Profile of a council reviewer"""
|
||
role: ReviewerRole
|
||
expertise_areas: list[str]
|
||
risk_tolerance: float # 0 (very conservative) to 1 (risk-taking)
|
||
priority_focus: list[str] # What this reviewer cares most about
|
||
veto_conditions: list[str] # Conditions that trigger automatic rejection
|
||
|
||
|
||
class CouncilReview:
|
||
"""
|
||
Multi-agent council for reviewing and approving suggestions.
|
||
|
||
Process:
|
||
1. Each reviewer evaluates the suggestion from their perspective
|
||
2. Votes are collected with reasoning
|
||
3. Final decision is made based on votes
|
||
4. Decision is tracked for learning
|
||
"""
|
||
|
||
# Reviewer profiles
|
||
REVIEWERS = {
|
||
ReviewerRole.SAFETY: ReviewerProfile(
|
||
role=ReviewerRole.SAFETY,
|
||
expertise_areas=["security", "access_control", "data_protection", "audit"],
|
||
risk_tolerance=0.2,
|
||
priority_focus=["no_security_regression", "audit_trail", "access_control"],
|
||
veto_conditions=[
|
||
"removes_security_check",
|
||
"bypasses_authentication",
|
||
"exposes_secrets",
|
||
"disables_audit_logging"
|
||
]
|
||
),
|
||
ReviewerRole.PERFORMANCE: ReviewerProfile(
|
||
role=ReviewerRole.PERFORMANCE,
|
||
expertise_areas=["latency", "throughput", "resource_usage", "scalability"],
|
||
risk_tolerance=0.6,
|
||
priority_focus=["response_time", "memory_usage", "scalability"],
|
||
veto_conditions=[
|
||
"infinite_loop_risk",
|
||
"memory_leak",
|
||
"blocking_operation_in_critical_path"
|
||
]
|
||
),
|
||
ReviewerRole.ARCHITECTURE: ReviewerProfile(
|
||
role=ReviewerRole.ARCHITECTURE,
|
||
expertise_areas=["design_patterns", "modularity", "maintainability", "coupling"],
|
||
risk_tolerance=0.5,
|
||
priority_focus=["clean_design", "separation_of_concerns", "extensibility"],
|
||
veto_conditions=[
|
||
"circular_dependency",
|
||
"god_class_creation",
|
||
"breaks_abstraction"
|
||
]
|
||
),
|
||
ReviewerRole.COMPLIANCE: ReviewerProfile(
|
||
role=ReviewerRole.COMPLIANCE,
|
||
expertise_areas=["governance", "policies", "regulations", "standards"],
|
||
risk_tolerance=0.3,
|
||
priority_focus=["policy_compliance", "audit_requirements", "documentation"],
|
||
veto_conditions=[
|
||
"violates_governance_policy",
|
||
"missing_audit_trail",
|
||
"unauthorized_tier_access"
|
||
]
|
||
),
|
||
ReviewerRole.QUALITY: ReviewerProfile(
|
||
role=ReviewerRole.QUALITY,
|
||
expertise_areas=["testing", "code_quality", "documentation", "reliability"],
|
||
risk_tolerance=0.4,
|
||
priority_focus=["test_coverage", "code_quality", "documentation"],
|
||
veto_conditions=[
|
||
"reduces_test_coverage",
|
||
"removes_validation",
|
||
"undocumented_behavior"
|
||
]
|
||
),
|
||
}
|
||
|
||
def __init__(self, base_path: str = "/opt/agent-governance"):
|
||
self.base_path = Path(base_path)
|
||
self.ledger_db = self.base_path / "ledger" / "governance.db"
|
||
self.decisions: list[Decision] = []
|
||
self._redis: Optional[redis.Redis] = None
|
||
self._setup_redis()
|
||
self._load_historical_decisions()
|
||
|
||
def _setup_redis(self):
|
||
"""Connect to DragonflyDB"""
|
||
try:
|
||
self._redis = redis.Redis(
|
||
host='127.0.0.1',
|
||
port=6379,
|
||
password='governance2026',
|
||
decode_responses=True
|
||
)
|
||
self._redis.ping()
|
||
except Exception:
|
||
self._redis = None
|
||
|
||
def _now(self) -> str:
|
||
return datetime.now(timezone.utc).isoformat()
|
||
|
||
def _load_historical_decisions(self):
|
||
"""Load past decisions for learning"""
|
||
if not self._redis:
|
||
return
|
||
|
||
try:
|
||
raw = self._redis.lrange("oversight:decisions", 0, 100)
|
||
for item in raw:
|
||
data = json.loads(item)
|
||
# Store for pattern matching
|
||
self.decisions.append(data)
|
||
except Exception:
|
||
pass
|
||
|
||
def review_suggestion(self, suggestion: Suggestion) -> Decision:
|
||
"""
|
||
Have the council review a suggestion.
|
||
|
||
Each reviewer votes, then final decision is made.
|
||
"""
|
||
votes = []
|
||
|
||
# Collect votes from each reviewer
|
||
for role, profile in self.REVIEWERS.items():
|
||
vote = self._get_reviewer_vote(suggestion, profile)
|
||
votes.append(vote)
|
||
|
||
# Make final decision based on votes
|
||
decision = self._make_decision(suggestion, votes)
|
||
|
||
# Persist decision
|
||
self._persist_decision(decision)
|
||
|
||
return decision
|
||
|
||
def _get_reviewer_vote(self, suggestion: Suggestion, profile: ReviewerProfile) -> Vote:
|
||
"""Get vote from a specific reviewer"""
|
||
concerns = []
|
||
conditions = []
|
||
vote_type = VoteType.APPROVE
|
||
confidence = 0.8
|
||
|
||
# Check veto conditions
|
||
for veto in profile.veto_conditions:
|
||
if self._check_veto_condition(suggestion, veto):
|
||
return Vote(
|
||
reviewer_role=profile.role,
|
||
vote=VoteType.REJECT,
|
||
reasoning=f"Veto condition triggered: {veto}",
|
||
concerns=[veto],
|
||
confidence=0.95
|
||
)
|
||
|
||
# Evaluate based on role
|
||
if profile.role == ReviewerRole.SAFETY:
|
||
vote_type, concerns, conditions, confidence = self._safety_review(suggestion)
|
||
elif profile.role == ReviewerRole.PERFORMANCE:
|
||
vote_type, concerns, conditions, confidence = self._performance_review(suggestion)
|
||
elif profile.role == ReviewerRole.ARCHITECTURE:
|
||
vote_type, concerns, conditions, confidence = self._architecture_review(suggestion)
|
||
elif profile.role == ReviewerRole.COMPLIANCE:
|
||
vote_type, concerns, conditions, confidence = self._compliance_review(suggestion)
|
||
elif profile.role == ReviewerRole.QUALITY:
|
||
vote_type, concerns, conditions, confidence = self._quality_review(suggestion)
|
||
|
||
# Adjust based on risk tolerance
|
||
if suggestion.risk == RiskLevel.CRITICAL and profile.risk_tolerance < 0.3:
|
||
if vote_type == VoteType.APPROVE:
|
||
vote_type = VoteType.NEEDS_MORE_INFO
|
||
concerns.append("Critical risk requires additional review")
|
||
|
||
reasoning = self._generate_reasoning(profile.role, suggestion, vote_type, concerns)
|
||
|
||
return Vote(
|
||
reviewer_role=profile.role,
|
||
vote=vote_type,
|
||
reasoning=reasoning,
|
||
concerns=concerns,
|
||
conditions=conditions,
|
||
confidence=confidence
|
||
)
|
||
|
||
def _check_veto_condition(self, suggestion: Suggestion, veto: str) -> bool:
|
||
"""Check if a veto condition is triggered"""
|
||
# Simple keyword matching for now
|
||
text = f"{suggestion.title} {suggestion.description} {suggestion.reasoning}".lower()
|
||
veto_keywords = veto.lower().replace("_", " ").split()
|
||
|
||
return all(kw in text for kw in veto_keywords)
|
||
|
||
def _safety_review(self, suggestion: Suggestion) -> tuple[VoteType, list, list, float]:
|
||
"""Safety reviewer evaluation"""
|
||
concerns = []
|
||
conditions = []
|
||
confidence = 0.8
|
||
|
||
# Check for security-related risks
|
||
security_keywords = ["credential", "secret", "password", "token", "auth", "access"]
|
||
text = f"{suggestion.title} {suggestion.description}".lower()
|
||
|
||
if any(kw in text for kw in security_keywords):
|
||
concerns.append("Changes involve security-sensitive components")
|
||
conditions.append("Must verify no credentials exposed")
|
||
|
||
if suggestion.risk in [RiskLevel.CRITICAL, RiskLevel.HIGH]:
|
||
concerns.append(f"High risk level: {suggestion.risk.value}")
|
||
conditions.append("Requires security review before implementation")
|
||
return VoteType.NEEDS_MORE_INFO, concerns, conditions, 0.6
|
||
|
||
if suggestion.auto_fixable and suggestion.risk == RiskLevel.LOW:
|
||
return VoteType.APPROVE, concerns, conditions, 0.9
|
||
|
||
return VoteType.APPROVE, concerns, conditions, confidence
|
||
|
||
def _performance_review(self, suggestion: Suggestion) -> tuple[VoteType, list, list, float]:
|
||
"""Performance reviewer evaluation"""
|
||
concerns = []
|
||
conditions = []
|
||
|
||
# Check for performance-related concerns
|
||
perf_keywords = ["loop", "query", "batch", "async", "timeout", "cache"]
|
||
text = f"{suggestion.title} {suggestion.description}".lower()
|
||
|
||
if "timeout" in text:
|
||
concerns.append("Timeout changes may affect system responsiveness")
|
||
conditions.append("Test under load before deploying")
|
||
|
||
if suggestion.estimated_effort in ["large", "epic"]:
|
||
concerns.append("Large change may have performance implications")
|
||
conditions.append("Benchmark before and after")
|
||
|
||
return VoteType.APPROVE, concerns, conditions, 0.75
|
||
|
||
def _architecture_review(self, suggestion: Suggestion) -> tuple[VoteType, list, list, float]:
|
||
"""Architecture reviewer evaluation"""
|
||
concerns = []
|
||
conditions = []
|
||
|
||
if len(suggestion.files_affected) > 5:
|
||
concerns.append(f"Affects {len(suggestion.files_affected)} files - wide blast radius")
|
||
conditions.append("Consider breaking into smaller changes")
|
||
|
||
if suggestion.impact == ImpactLevel.TRANSFORMATIVE:
|
||
concerns.append("Transformative change requires architecture review")
|
||
return VoteType.NEEDS_MORE_INFO, concerns, conditions, 0.5
|
||
|
||
return VoteType.APPROVE, concerns, conditions, 0.8
|
||
|
||
def _compliance_review(self, suggestion: Suggestion) -> tuple[VoteType, list, list, float]:
|
||
"""Compliance reviewer evaluation"""
|
||
concerns = []
|
||
conditions = []
|
||
|
||
# Check governance keywords
|
||
gov_keywords = ["policy", "tier", "permission", "violation", "audit"]
|
||
text = f"{suggestion.title} {suggestion.description}".lower()
|
||
|
||
if any(kw in text for kw in gov_keywords):
|
||
concerns.append("Changes may affect governance policies")
|
||
conditions.append("Verify compliance with tier policies")
|
||
|
||
if "revocation" in text or "promotion" in text:
|
||
concerns.append("Affects agent trust system")
|
||
conditions.append("Must maintain audit trail")
|
||
|
||
return VoteType.APPROVE, concerns, conditions, 0.85
|
||
|
||
def _quality_review(self, suggestion: Suggestion) -> tuple[VoteType, list, list, float]:
|
||
"""Quality reviewer evaluation"""
|
||
concerns = []
|
||
conditions = []
|
||
|
||
if not suggestion.fix_steps or len(suggestion.fix_steps) < 2:
|
||
concerns.append("Fix steps are not well-defined")
|
||
conditions.append("Document implementation steps")
|
||
|
||
if suggestion.auto_fixable:
|
||
conditions.append("Auto-fix must include verification step")
|
||
|
||
return VoteType.APPROVE, concerns, conditions, 0.8
|
||
|
||
def _generate_reasoning(
|
||
self,
|
||
role: ReviewerRole,
|
||
suggestion: Suggestion,
|
||
vote: VoteType,
|
||
concerns: list[str]
|
||
) -> str:
|
||
"""Generate reasoning text for a vote"""
|
||
# Handle both enum and string values for risk
|
||
risk_val = suggestion.risk.value if hasattr(suggestion.risk, 'value') else suggestion.risk
|
||
role_val = role.value if hasattr(role, 'value') else role
|
||
vote_val = vote.value if hasattr(vote, 'value') else vote
|
||
|
||
templates = {
|
||
VoteType.APPROVE: f"[{role_val.upper()}] Approved: {suggestion.title}. "
|
||
f"Risk level ({risk_val}) is acceptable.",
|
||
VoteType.REJECT: f"[{role_val.upper()}] Rejected: {suggestion.title}. "
|
||
f"Concerns: {'; '.join(concerns) if concerns else 'See details'}",
|
||
VoteType.NEEDS_MORE_INFO: f"[{role_val.upper()}] Needs review: {suggestion.title}. "
|
||
f"Additional information required.",
|
||
VoteType.ABSTAIN: f"[{role_val.upper()}] Abstained: Outside expertise area."
|
||
}
|
||
|
||
return templates.get(vote, f"[{role_val.upper()}] {vote_val}: {suggestion.title}")
|
||
|
||
def _make_decision(self, suggestion: Suggestion, votes: list[Vote]) -> Decision:
|
||
"""Make final decision based on votes"""
|
||
approve_count = sum(1 for v in votes if v.vote == VoteType.APPROVE)
|
||
reject_count = sum(1 for v in votes if v.vote == VoteType.REJECT)
|
||
needs_info_count = sum(1 for v in votes if v.vote == VoteType.NEEDS_MORE_INFO)
|
||
|
||
all_concerns = []
|
||
all_conditions = []
|
||
for v in votes:
|
||
all_concerns.extend(v.concerns)
|
||
all_conditions.extend(v.conditions)
|
||
|
||
# Decision logic
|
||
if reject_count >= 2:
|
||
decision_type = DecisionType.REJECT
|
||
reasoning = f"Rejected by {reject_count} reviewers. Concerns: {'; '.join(all_concerns[:3])}"
|
||
auto_fix = False
|
||
manual = True
|
||
|
||
elif needs_info_count >= 2:
|
||
decision_type = DecisionType.DEFER
|
||
reasoning = f"Deferred - {needs_info_count} reviewers need more information."
|
||
auto_fix = False
|
||
manual = True
|
||
|
||
elif approve_count >= 4 and suggestion.auto_fixable and suggestion.risk in [RiskLevel.LOW, RiskLevel.TRIVIAL]:
|
||
decision_type = DecisionType.AUTO_APPROVE
|
||
reasoning = f"Auto-approved by council ({approve_count}/{len(votes)} votes). Low risk, auto-fixable."
|
||
auto_fix = True
|
||
manual = False
|
||
|
||
elif approve_count >= 3:
|
||
decision_type = DecisionType.HUMAN_APPROVE
|
||
reasoning = f"Approved for human implementation ({approve_count}/{len(votes)} votes)."
|
||
auto_fix = False
|
||
manual = True
|
||
|
||
else:
|
||
decision_type = DecisionType.ESCALATE
|
||
reasoning = f"No consensus reached. Votes: {approve_count} approve, {reject_count} reject, {needs_info_count} need info."
|
||
auto_fix = False
|
||
manual = True
|
||
|
||
return Decision(
|
||
id="",
|
||
suggestion_id=suggestion.id,
|
||
decision=decision_type,
|
||
votes=votes,
|
||
final_reasoning=reasoning,
|
||
conditions=list(set(all_conditions)),
|
||
requires_manual_oversight=manual,
|
||
auto_fix_approved=auto_fix
|
||
)
|
||
|
||
def _persist_decision(self, decision: Decision):
|
||
"""Persist decision to storage"""
|
||
if not self._redis:
|
||
self.decisions.append(decision)
|
||
return
|
||
|
||
# Convert Vote objects to dicts
|
||
decision_dict = asdict(decision)
|
||
decision_dict['votes'] = [asdict(v) if hasattr(v, '__dict__') else v for v in decision.votes]
|
||
|
||
self._redis.lpush(
|
||
"oversight:decisions",
|
||
json.dumps(decision_dict, default=str)
|
||
)
|
||
|
||
# Index by decision type
|
||
decision_val = decision.decision.value if hasattr(decision.decision, 'value') else decision.decision
|
||
self._redis.sadd(f"oversight:decisions:{decision_val}", decision.id)
|
||
|
||
# Keep history manageable
|
||
self._redis.ltrim("oversight:decisions", 0, 499)
|
||
|
||
def record_outcome(
|
||
self,
|
||
decision_id: str,
|
||
outcome: str,
|
||
notes: str = "",
|
||
lessons: str = ""
|
||
) -> bool:
|
||
"""Record the outcome of an implemented decision"""
|
||
if not self._redis:
|
||
return False
|
||
|
||
self._redis.hset(f"oversight:decision:{decision_id}", mapping={
|
||
"implemented_at": self._now(),
|
||
"outcome": outcome,
|
||
"outcome_notes": notes,
|
||
"lessons_learned": lessons
|
||
})
|
||
|
||
# Update learning data
|
||
self._redis.lpush("oversight:lessons", json.dumps({
|
||
"decision_id": decision_id,
|
||
"outcome": outcome,
|
||
"lessons": lessons,
|
||
"timestamp": self._now()
|
||
}))
|
||
|
||
return True
|
||
|
||
def get_decisions(
|
||
self,
|
||
decision_type: Optional[DecisionType] = None,
|
||
limit: int = 50
|
||
) -> list[dict]:
|
||
"""Get decisions with optional filter"""
|
||
if not self._redis:
|
||
decisions = self.decisions
|
||
if decision_type:
|
||
decisions = [d for d in decisions
|
||
if (d.get('decision') if isinstance(d, dict) else d.decision) == decision_type.value]
|
||
return decisions[:limit]
|
||
|
||
raw = self._redis.lrange("oversight:decisions", 0, limit - 1)
|
||
decisions = []
|
||
|
||
for item in raw:
|
||
try:
|
||
data = json.loads(item)
|
||
if decision_type and data.get('decision') != decision_type.value:
|
||
continue
|
||
decisions.append(data)
|
||
except Exception:
|
||
continue
|
||
|
||
return decisions
|
||
|
||
def get_summary(self) -> dict:
|
||
"""Get summary of council decisions"""
|
||
decisions = self.get_decisions(limit=500)
|
||
|
||
by_type = {d.value: 0 for d in DecisionType}
|
||
outcomes = {"success": 0, "failure": 0, "partial": 0, "pending": 0}
|
||
auto_approved = 0
|
||
|
||
for d in decisions:
|
||
decision_type = d.get('decision') if isinstance(d, dict) else d.decision
|
||
by_type[decision_type] = by_type.get(decision_type, 0) + 1
|
||
|
||
if d.get('auto_fix_approved'):
|
||
auto_approved += 1
|
||
|
||
outcome = d.get('outcome')
|
||
if outcome in outcomes:
|
||
outcomes[outcome] += 1
|
||
else:
|
||
outcomes["pending"] += 1
|
||
|
||
return {
|
||
"total_decisions": len(decisions),
|
||
"by_type": by_type,
|
||
"outcomes": outcomes,
|
||
"auto_approved": auto_approved,
|
||
"learning_entries": self._redis.llen("oversight:lessons") if self._redis else 0
|
||
}
|
||
|
||
def get_lessons_learned(self, limit: int = 20) -> list[dict]:
|
||
"""Get lessons learned from past decisions"""
|
||
if not self._redis:
|
||
return []
|
||
|
||
raw = self._redis.lrange("oversight:lessons", 0, limit - 1)
|
||
lessons = []
|
||
|
||
for item in raw:
|
||
try:
|
||
lessons.append(json.loads(item))
|
||
except Exception:
|
||
continue
|
||
|
||
return lessons
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import argparse
|
||
from .bug_watcher import BugWindowWatcher
|
||
from .suggestion_engine import SuggestionEngine
|
||
|
||
parser = argparse.ArgumentParser(description="Council Review")
|
||
parser.add_argument("command", choices=["review", "decisions", "lessons", "status"])
|
||
parser.add_argument("--phase", type=int)
|
||
parser.add_argument("--json", action="store_true")
|
||
|
||
args = parser.parse_args()
|
||
|
||
council = CouncilReview()
|
||
|
||
if args.command == "review":
|
||
# Get anomalies and suggestions
|
||
watcher = BugWindowWatcher()
|
||
engine = SuggestionEngine()
|
||
|
||
anomalies = watcher.scan_all_phases() if not args.phase else watcher.scan_phase(args.phase)
|
||
|
||
print(f"\n{'='*60}")
|
||
print(f"COUNCIL REVIEW")
|
||
print(f"{'='*60}")
|
||
|
||
for anomaly in anomalies[:5]: # Limit for demo
|
||
suggestions = engine.generate_suggestions(anomaly)
|
||
|
||
for suggestion in suggestions[:2]: # Top 2 per anomaly
|
||
decision = council.review_suggestion(suggestion)
|
||
|
||
icon = {"auto_approve": "🤖", "human_approve": "✅", "defer": "⏳", "reject": "❌", "escalate": "⚠️"}.get(decision.decision.value, "❓")
|
||
print(f"\n{icon} {decision.decision.value.upper()}: {suggestion.title}")
|
||
print(f" {decision.final_reasoning}")
|
||
|
||
if decision.conditions:
|
||
print(f" Conditions: {', '.join(decision.conditions[:3])}")
|
||
|
||
print(f" Votes: ", end="")
|
||
for v in decision.votes:
|
||
vote_icon = {"approve": "👍", "reject": "👎", "needs_more_info": "❓", "abstain": "➖"}.get(v.vote.value, "❓")
|
||
print(f"{vote_icon}{v.reviewer_role.value[0].upper()} ", end="")
|
||
print()
|
||
|
||
elif args.command == "decisions":
|
||
decisions = council.get_decisions()
|
||
if args.json:
|
||
print(json.dumps(decisions, indent=2, default=str))
|
||
else:
|
||
for d in decisions[:20]:
|
||
print(f"[{d['id']}] {d['decision']}: {d['final_reasoning'][:60]}...")
|
||
|
||
elif args.command == "lessons":
|
||
lessons = council.get_lessons_learned()
|
||
if args.json:
|
||
print(json.dumps(lessons, indent=2))
|
||
else:
|
||
for l in lessons:
|
||
print(f"[{l['decision_id']}] {l['outcome']}: {l['lessons'][:80]}...")
|
||
|
||
elif args.command == "status":
|
||
summary = council.get_summary()
|
||
if args.json:
|
||
print(json.dumps(summary, indent=2))
|
||
else:
|
||
print(f"\nCouncil Status")
|
||
print(f"Total Decisions: {summary['total_decisions']}")
|
||
print(f"Auto-Approved: {summary['auto_approved']}")
|
||
print(f"Lessons Learned: {summary['learning_entries']}")
|