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>
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()
|