80 lines
3.1 KiB
Python
80 lines
3.1 KiB
Python
|
|
#!/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()
|