#!/usr/bin/env python3 """Auto-promote reinforced candidates + expire stale ones. Phase 10: reinforcement-based auto-promotion. Candidates referenced by 3+ interactions with confidence >= 0.7 graduate to active. Candidates unreinforced for 14+ days are auto-rejected. Usage: python3 scripts/auto_promote_reinforced.py [--base-url URL] [--dry-run] """ from __future__ import annotations import argparse import json import os import sys # Allow importing from src/ when run from repo root sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) from atocore.memory.service import auto_promote_reinforced, expire_stale_candidates def main() -> None: parser = argparse.ArgumentParser(description="Auto-promote + expire candidates") parser.add_argument("--dry-run", action="store_true", help="Report only, don't change anything") parser.add_argument("--min-refs", type=int, default=3, help="Min reference_count for promotion") parser.add_argument("--min-confidence", type=float, default=0.7, help="Min confidence for promotion") parser.add_argument("--expire-days", type=int, default=14, help="Days before unreinforced candidates expire") args = parser.parse_args() if args.dry_run: print("DRY RUN — no changes will be made") # For dry-run, query directly and report from atocore.models.database import get_connection from datetime import datetime, timedelta, timezone cutoff_promote = (datetime.now(timezone.utc) - timedelta(days=args.expire_days)).strftime("%Y-%m-%d %H:%M:%S") cutoff_expire = cutoff_promote with get_connection() as conn: promotable = conn.execute( "SELECT id, content, memory_type, project, confidence, reference_count " "FROM memories WHERE status = 'candidate' " "AND COALESCE(reference_count, 0) >= ? AND confidence >= ? " "AND last_referenced_at >= ?", (args.min_refs, args.min_confidence, cutoff_promote), ).fetchall() expirable = conn.execute( "SELECT id, content, memory_type, project " "FROM memories WHERE status = 'candidate' " "AND COALESCE(reference_count, 0) = 0 AND created_at < ?", (cutoff_expire,), ).fetchall() print(f"\nWould promote {len(promotable)} candidates:") for r in promotable: print(f" [{r['memory_type']}] refs={r['reference_count']} conf={r['confidence']:.2f} | {r['content'][:80]}...") print(f"\nWould expire {len(expirable)} stale candidates:") for r in expirable: print(f" [{r['memory_type']}] {r['project'] or 'global'} | {r['content'][:80]}...") return promoted = auto_promote_reinforced( min_reference_count=args.min_refs, min_confidence=args.min_confidence, ) expired = expire_stale_candidates(max_age_days=args.expire_days) print(f"promoted={len(promoted)} expired={len(expired)}") if promoted: print(f"Promoted IDs: {promoted}") if expired: print(f"Expired IDs: {expired}") if __name__ == "__main__": main()