feat: "Make It Actually Useful" sprint — observability + Phase 10
Pipeline observability: - Retrieval harness runs nightly (Step E in batch-extract.sh) - Pipeline summary persisted to project state after each run (pipeline_last_run, pipeline_summary, retrieval_harness_result) - Dashboard enhanced: interaction total + by_client, pipeline health (last_run, hours_since, harness results, triage stats), dynamic project list from registry Phase 10 — reinforcement-based auto-promotion: - auto_promote_reinforced(): candidates with reference_count >= 3 and confidence >= 0.7 auto-graduate to active - expire_stale_candidates(): candidates unreinforced for 14+ days auto-rejected to prevent unbounded queue growth - Both wired into nightly cron (Step B2) - Batch script: scripts/auto_promote_reinforced.py (--dry-run support) Knowledge seeding: - scripts/seed_project_state.py: 26 curated Trusted Project State entries across p04-gigabit, p05-interferometer, p06-polisher, atomizer-v2, abb-space, atocore (decisions, requirements, facts, contacts, milestones) Tests: 299 → 303 (4 new Phase 10 tests) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
79
scripts/auto_promote_reinforced.py
Normal file
79
scripts/auto_promote_reinforced.py
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user