"""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_entity_card(entity) -> str: """Phase 5: entity candidate card with promote/reject.""" eid = _escape(entity.id) name = _escape(entity.name) etype = _escape(entity.entity_type) project = _escape(entity.project or "(global)") desc = _escape(entity.description or "") conf = f"{entity.confidence:.2f}" src_refs = entity.source_refs or [] source_display = _escape(", ".join(src_refs[:3])) if src_refs else "(no provenance)" return f"""
[entity · {etype}] {project} conf {conf} · src: {source_display}
{name}
{desc}
View in wiki →
""" _ENTITY_TRIAGE_SCRIPT = """ """ _ENTITY_TRIAGE_CSS = """ """ def _render_graduation_bar() -> str: """The 'Graduate memories → entity candidates' control bar.""" from atocore.projects.registry import load_project_registry try: projects = load_project_registry() options = '' + "".join( f'' for p in projects ) except Exception: options = '' return f"""
Scans active memories, asks the LLM "does this describe a typed entity?", and creates entity candidates. Review them in the Entity section below.
""" _GRADUATION_SCRIPT = """ """ def render_triage_page(limit: int = 100) -> str: """Render the full triage page with pending memory + entity candidates.""" from atocore.engineering.service import get_entities try: mem_candidates = get_memories(status="candidate", limit=limit) except Exception as e: body = f"

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

" return render_html("Triage — AtoCore", body, breadcrumbs=[("Wiki", "/wiki"), ("Triage", "")]) try: entity_candidates = get_entities(status="candidate", limit=limit) except Exception as e: entity_candidates = [] total = len(mem_candidates) + len(entity_candidates) graduation_bar = _render_graduation_bar() if total == 0: body = _TRIAGE_CSS + _ENTITY_TRIAGE_CSS + f"""

Triage Queue

{graduation_bar}

🎉 No candidates to review.

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

Use the 🎓 Graduate memories button above to propose new entity candidates from existing memories.

""" + _GRADUATION_SCRIPT return render_html("Triage — AtoCore", body, breadcrumbs=[("Wiki", "/wiki"), ("Triage", "")]) # Memory cards mem_cards = "".join(_render_candidate_card(c) for c in mem_candidates) # Entity cards ent_cards_html = "" if entity_candidates: ent_cards = "".join(_render_entity_card(e) for e in entity_candidates) ent_cards_html = f"""

🔧 Entity Candidates ({len(entity_candidates)})

Typed graph entries awaiting review. Promoting an entity connects it to the engineering knowledge graph (subsystems, requirements, decisions, etc.).

{ent_cards} """ body = _TRIAGE_CSS + _ENTITY_TRIAGE_CSS + f"""

Triage Queue

{len(mem_candidates)} memory · {len(entity_candidates)} entity
Review candidates 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 memory queue through 3-tier LLM triage on the host. Sonnet → Opus → auto-discard. Only genuinely ambiguous items land here.
{graduation_bar}

📝 Memory Candidates ({len(mem_candidates)})

{mem_cards} {ent_cards_html} """ + _TRIAGE_SCRIPT + _ENTITY_TRIAGE_SCRIPT + _GRADUATION_SCRIPT return render_html( "Triage — AtoCore", body, breadcrumbs=[("Wiki", "/wiki"), ("Triage", "")], )