""" Manual memory distillation endpoints. POST /distill/short — roll session logs → MEMORY_SHORT.md (no LLM) POST /distill/mid — summarize short → MEMORY_MID.md (LLM) POST /distill/long — integrate mid → MEMORY_LONG.md (LLM) POST /distill/all — run all three in sequence All endpoints require ?user=&persona= query params so distillation targets the correct persona. Without them, the request is rejected (no silent fallback to server defaults — that caused wrong-user distillation in a multi-user setup). """ from fastapi import APIRouter, HTTPException, Query from memory_distiller import distill_short, distill_mid, distill_long from persona import validate as validate_persona, set_context import scheduler router = APIRouter(prefix="/distill") def _resolve(user: str, persona: str) -> tuple[str, str]: """Validate and set persona context. Raises 404 if the persona doesn't exist.""" try: u, p = validate_persona(user, persona) except Exception: raise HTTPException(status_code=404, detail=f"Persona not found: {user}/{persona}") set_context(u, p) return u, p @router.get("/status") async def distill_status() -> dict: """Show auto-distillation schedule and next run times.""" from config import settings return { "enabled": settings.auto_distill, "jobs": scheduler.status(), "config": { "short": settings.auto_distill_short, "mid": settings.auto_distill_mid, "long": settings.auto_distill_long, }, } @router.post("/short") async def do_distill_short( user: str = Query(...), persona: str = Query(...), ) -> dict: u, p = _resolve(user, persona) return {"ok": True, **distill_short(u, p)} @router.post("/mid") async def do_distill_mid( user: str = Query(...), persona: str = Query(...), ) -> dict: u, p = _resolve(user, persona) result = await distill_mid(u, p) return {"ok": "error" not in result, **result} @router.post("/long") async def do_distill_long( user: str = Query(...), persona: str = Query(...), ) -> dict: u, p = _resolve(user, persona) result = await distill_long(u, p) return {"ok": "error" not in result, **result} @router.post("/all") async def do_distill_all( user: str = Query(...), persona: str = Query(...), ) -> dict: u, p = _resolve(user, persona) short_result = distill_short(u, p) mid_result = await distill_mid(u, p) if "error" in mid_result: return {"ok": False, "short": short_result, "mid": mid_result} long_result = await distill_long(u, p) return { "ok": "error" not in long_result, "short": short_result, "mid": mid_result, "long": long_result, }