"""Human triage UI for AtoCore candidate memories. Renders a lightweight HTML page at /admin/triage with all pending candidate memories, each with inline Promote / Reject / Edit buttons. No framework, no JS build, no database — reads candidates from the AtoCore DB and posts back to the existing REST endpoints. Design principle: the user should be able to triage 20 candidates in 60 seconds from any browser. Keyboard shortcuts (y/n/e/s) make it feel like email triage (archive/delete). """ from __future__ import annotations import html as _html from atocore.engineering.wiki import render_html from atocore.memory.service import get_memories VALID_TYPES = ["identity", "preference", "project", "episodic", "knowledge", "adaptation"] def _escape(s: str | None) -> str: return _html.escape(s or "", quote=True) def _render_candidate_card(cand) -> str: """One candidate row with inline forms for promote/reject/edit.""" mid = _escape(cand.id) content = _escape(cand.content) memory_type = _escape(cand.memory_type) project = _escape(cand.project or "") project_display = project or "(global)" confidence = f"{cand.confidence:.2f}" refs = cand.reference_count or 0 created = _escape(str(cand.created_at or "")) tags = cand.domain_tags or [] tags_str = _escape(", ".join(tags)) valid_until = _escape(cand.valid_until or "") # Strip time portion for HTML date input valid_until_date = valid_until[:10] if valid_until else "" type_options = "".join( f'' for t in VALID_TYPES ) # Tag badges rendered from current tags badges_html = "" if tags: badges_html = '
' + "".join( f'{_escape(t)}' for t in tags ) + '
' return f"""
[{memory_type}] {project_display} conf {confidence} · refs {refs} · {created[:16]}
{badges_html}
""" _TRIAGE_SCRIPT = """ """ _TRIAGE_CSS = """ """ def render_triage_page(limit: int = 100) -> str: """Render the full triage page with all pending candidates.""" try: candidates = get_memories(status="candidate", limit=limit) except Exception as e: body = f"

Error loading candidates: {_escape(str(e))}

" return render_html("Triage — AtoCore", body, breadcrumbs=[("Wiki", "/wiki"), ("Triage", "")]) if not candidates: body = _TRIAGE_CSS + """

Triage Queue

🎉 No candidates to review.

The auto-triage pipeline keeps this queue empty unless something needs your judgment.

""" return render_html("Triage — AtoCore", body, breadcrumbs=[("Wiki", "/wiki"), ("Triage", "")]) cards_html = "".join(_render_candidate_card(c) for c in candidates) body = _TRIAGE_CSS + f"""

Triage Queue

{len(candidates)} pending
Review candidate memories the auto-triage wasn't sure about. Edit the content if needed, then promote or reject. Shortcuts: Y promote · N reject · E edit · S scroll to next.
Sends the full queue through LLM triage on the host. Promotes durable facts, rejects noise, leaves only ambiguous items here for you.
{cards_html} """ + _TRIAGE_SCRIPT return render_html( "Triage — AtoCore", body, breadcrumbs=[("Wiki", "/wiki"), ("Triage", "")], )