""" Lab 7.7: Build an Autoresearch Loop -- SOLUTION Self-improving experiment loop with integrity guards. """ import hashlib import json import time import random from dataclasses import dataclass, asdict from datetime import datetime, timezone @dataclass class ExperimentRun: run: int status: str metric_name: str metric_value: float metric_unit: str metric_direction: str description: str code_hash: str delta_pct: float | None = None timestamp: str = "" def __post_init__(self): if not self.timestamp: self.timestamp = datetime.now(timezone.utc).isoformat() def hash_code(file_path: str) -> str: try: with open(file_path, "rb") as f: return hashlib.sha256(f.read()).hexdigest()[:12] except FileNotFoundError: return "no-file" def detect_grind(previous_hash: str | None, current_hash: str) -> bool: if previous_hash is None: return False return previous_hash == current_hash def median_score(scores: list[float]) -> float: sorted_scores = sorted(scores) return sorted_scores[len(sorted_scores) // 2] class AutoresearchLoop: def __init__(self, log_path: str = "experiments.jsonl"): self.log_path = log_path self.run_count = 0 self.last_code_hash = None self.history = [] def setup_baseline(self) -> float: print(" Running baseline...") time.sleep(0.2) return 52.0 # baseline latency in ms def propose_change(self) -> str: changes = [ "Remove N+1 count queries by pre-aggregating", "Memoize DB reads to avoid repeated sync work", "Add connection pooling to DB handler", "Cache repeated API responses in memory", "Move heavy computation to background worker", "Optimize hot loop with list comprehension", ] return random.choice(changes) def apply_change(self, change_desc: str): print(f" Applying: {change_desc}") time.sleep(0.1) def revert_change(self): print(" Reverting change...") time.sleep(0.05) def measure(self) -> float: time.sleep(0.15) return max(30.0, 52.0 + random.gauss(-3, 4)) def log_experiment(self, run: ExperimentRun): with open(self.log_path, "a") as f: f.write(json.dumps(asdict(run)) + "\n") self.history.append(run.metric_value) def should_keep(self, baseline: float, current: float, direction: str) -> bool: if direction == "minimize": return current < baseline else: return current > baseline def run(self, max_iterations: int = 10): print("Starting autoresearch loop...") print(f"Log: {self.log_path}") baseline = self.setup_baseline() print(f"Baseline: {baseline} ms") self.log_experiment(ExperimentRun( run=0, status="keep", metric_name="latency", metric_value=baseline, metric_unit="ms", metric_direction="minimize", description="Baseline measurement", code_hash=hash_code("agent.py") )) for i in range(max_iterations): print(f"\n--- Run {i+1} ---") change = self.propose_change() print(f"Proposed: {change}") current_hash = hash_code("agent.py") if detect_grind(self.last_code_hash, current_hash): print("[WARN] Grind detected! Skipping (code unchanged)") continue self.last_code_hash = current_hash self.apply_change(change) new_value = self.measure() keep = self.should_keep(baseline, new_value, "minimize") delta = ((new_value - baseline) / baseline) * 100 if not keep: self.revert_change() run = ExperimentRun( run=i+1, status="keep" if keep else "discard", metric_name="latency", metric_value=new_value, metric_unit="ms", metric_direction="minimize", description=change, code_hash=current_hash, delta_pct=round(delta, 2) ) self.log_experiment(run) if keep: baseline = new_value print(f"+ KEPT ({new_value:.1f} ms, D={delta:+.2f}%)") else: print(f"- DISCARDED ({new_value:.1f} ms, D={delta:+.2f}%)") if self.history: med = median_score(self.history) print(f"\n{'='*40}") print(f"Baseline: {52.0} ms") print(f"Best: {min(self.history):.1f} ms") print(f"Median: {med:.1f} ms") improvement = ((52.0 - med) / 52.0) * 100 print(f"Improvement: {improvement:+.1f}% (using median, not best)") print(f"Runs logged: {len(self.history)}") print(f"Experiment log: {self.log_path}") if __name__ == "__main__": loop = AutoresearchLoop() loop.run(max_iterations=8) print("\nView results: cat experiments.jsonl | python -m json.tool")