refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
"""Cheshire Cat memory management routes."""
|
|
|
|
|
|
|
|
|
|
from typing import Optional
|
|
|
|
|
from fastapi import APIRouter, Form
|
2026-04-15 15:43:18 +03:00
|
|
|
from fastapi.responses import JSONResponse
|
refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
import globals
|
|
|
|
|
from routes.models import MemoryDeleteRequest, MemoryEditRequest, MemoryCreateRequest
|
|
|
|
|
from utils.logger import get_logger
|
|
|
|
|
|
|
|
|
|
logger = get_logger('api')
|
|
|
|
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/memory/status")
|
|
|
|
|
async def get_cat_memory_status():
|
|
|
|
|
"""Get Cheshire Cat connection status and feature flag."""
|
|
|
|
|
from utils.cat_client import cat_adapter
|
|
|
|
|
is_healthy = await cat_adapter.health_check()
|
|
|
|
|
return {
|
|
|
|
|
"enabled": globals.USE_CHESHIRE_CAT,
|
|
|
|
|
"healthy": is_healthy,
|
|
|
|
|
"url": globals.CHESHIRE_CAT_URL,
|
|
|
|
|
"circuit_breaker_active": cat_adapter._is_circuit_broken(),
|
|
|
|
|
"consecutive_failures": cat_adapter._consecutive_failures
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/memory/toggle")
|
|
|
|
|
async def toggle_cat_integration(enabled: bool = Form(...)):
|
|
|
|
|
"""Toggle Cheshire Cat integration on/off."""
|
|
|
|
|
globals.USE_CHESHIRE_CAT = enabled
|
|
|
|
|
logger.info(f"🐱 Cheshire Cat integration {'ENABLED' if enabled else 'DISABLED'}")
|
|
|
|
|
|
|
|
|
|
# Persist so it survives restarts
|
|
|
|
|
try:
|
|
|
|
|
from config_manager import config_manager
|
|
|
|
|
config_manager.set("memory.use_cheshire_cat", enabled, persist=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"enabled": globals.USE_CHESHIRE_CAT,
|
|
|
|
|
"message": f"Cheshire Cat {'enabled' if enabled else 'disabled'}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/memory/stats")
|
|
|
|
|
async def get_memory_stats():
|
|
|
|
|
"""Get memory collection statistics from Cheshire Cat (point counts per collection)."""
|
|
|
|
|
from utils.cat_client import cat_adapter
|
|
|
|
|
stats = await cat_adapter.get_memory_stats()
|
|
|
|
|
if stats is None:
|
2026-04-15 15:43:18 +03:00
|
|
|
return JSONResponse(status_code=502, content={"success": False, "error": "Could not reach Cheshire Cat"})
|
refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
return {"success": True, "collections": stats.get("collections", [])}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/memory/facts")
|
|
|
|
|
async def get_memory_facts():
|
|
|
|
|
"""Get all declarative memory facts (learned knowledge about users)."""
|
|
|
|
|
from utils.cat_client import cat_adapter
|
|
|
|
|
facts = await cat_adapter.get_all_facts()
|
|
|
|
|
return {"success": True, "facts": facts, "count": len(facts)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/memory/episodic")
|
|
|
|
|
async def get_episodic_memories():
|
|
|
|
|
"""Get all episodic memories (conversation snippets)."""
|
|
|
|
|
from utils.cat_client import cat_adapter
|
|
|
|
|
result = await cat_adapter.get_memory_points(collection="episodic", limit=100)
|
|
|
|
|
if result is None:
|
2026-04-15 15:43:18 +03:00
|
|
|
return JSONResponse(status_code=502, content={"success": False, "error": "Could not reach Cheshire Cat"})
|
refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
|
|
|
|
|
memories = []
|
|
|
|
|
for point in result.get("points", []):
|
|
|
|
|
payload = point.get("payload", {})
|
|
|
|
|
memories.append({
|
|
|
|
|
"id": point.get("id"),
|
|
|
|
|
"content": payload.get("page_content", ""),
|
|
|
|
|
"metadata": payload.get("metadata", {}),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {"success": True, "memories": memories, "count": len(memories)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/memory/consolidate")
|
|
|
|
|
async def trigger_memory_consolidation():
|
|
|
|
|
"""Manually trigger memory consolidation (sleep consolidation process)."""
|
|
|
|
|
from utils.cat_client import cat_adapter
|
|
|
|
|
logger.info("🌙 Manual memory consolidation triggered via API")
|
|
|
|
|
result = await cat_adapter.trigger_consolidation()
|
|
|
|
|
if result is None:
|
2026-04-15 15:43:18 +03:00
|
|
|
return JSONResponse(status_code=500, content={"success": False, "error": "Consolidation failed or timed out"})
|
refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
return {"success": True, "result": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/memory/delete")
|
|
|
|
|
async def delete_all_memories(request: MemoryDeleteRequest):
|
|
|
|
|
"""
|
|
|
|
|
Delete ALL of Miku's memories. Requires exact confirmation string.
|
|
|
|
|
|
|
|
|
|
The confirmation field must be exactly:
|
|
|
|
|
"Yes, I am deleting Miku's memories fully."
|
|
|
|
|
|
|
|
|
|
This is destructive and irreversible.
|
|
|
|
|
"""
|
|
|
|
|
REQUIRED_CONFIRMATION = "Yes, I am deleting Miku's memories fully."
|
|
|
|
|
|
|
|
|
|
if request.confirmation != REQUIRED_CONFIRMATION:
|
|
|
|
|
logger.warning(f"Memory deletion rejected: wrong confirmation string")
|
2026-04-15 15:43:18 +03:00
|
|
|
return JSONResponse(status_code=400, content={
|
refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
"success": False,
|
|
|
|
|
"error": "Confirmation string does not match. "
|
|
|
|
|
f"Expected exactly: \"{REQUIRED_CONFIRMATION}\""
|
2026-04-15 15:43:18 +03:00
|
|
|
})
|
refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
|
|
|
|
|
from utils.cat_client import cat_adapter
|
|
|
|
|
logger.warning("⚠️ MEMORY DELETION CONFIRMED — wiping all memories!")
|
|
|
|
|
|
|
|
|
|
# Wipe vector memories (episodic + declarative)
|
|
|
|
|
wipe_success = await cat_adapter.wipe_all_memories()
|
|
|
|
|
|
|
|
|
|
# Also clear conversation history
|
|
|
|
|
history_success = await cat_adapter.wipe_conversation_history()
|
|
|
|
|
|
|
|
|
|
if wipe_success:
|
|
|
|
|
logger.warning("🗑️ All Miku memories have been deleted.")
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": "All memories have been permanently deleted.",
|
|
|
|
|
"vector_memory_wiped": wipe_success,
|
|
|
|
|
"conversation_history_cleared": history_success
|
|
|
|
|
}
|
|
|
|
|
else:
|
2026-04-15 15:43:18 +03:00
|
|
|
return JSONResponse(status_code=500, content={
|
refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
"success": False,
|
|
|
|
|
"error": "Failed to wipe memory collections. Check Cat connection."
|
2026-04-15 15:43:18 +03:00
|
|
|
})
|
refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/memory/point/{collection}/{point_id}")
|
|
|
|
|
async def delete_single_memory_point(collection: str, point_id: str):
|
|
|
|
|
"""Delete a single memory point by collection and ID."""
|
|
|
|
|
from utils.cat_client import cat_adapter
|
|
|
|
|
success = await cat_adapter.delete_memory_point(collection, point_id)
|
|
|
|
|
if success:
|
|
|
|
|
return {"success": True, "deleted": point_id}
|
|
|
|
|
else:
|
2026-04-15 15:43:18 +03:00
|
|
|
return JSONResponse(status_code=500, content={"success": False, "error": f"Failed to delete point {point_id}"})
|
refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/memory/point/{collection}/{point_id}")
|
|
|
|
|
async def edit_memory_point(collection: str, point_id: str, request: MemoryEditRequest):
|
|
|
|
|
"""Edit an existing memory point's content and/or metadata."""
|
|
|
|
|
from utils.cat_client import cat_adapter
|
|
|
|
|
success = await cat_adapter.update_memory_point(
|
|
|
|
|
collection=collection,
|
|
|
|
|
point_id=point_id,
|
|
|
|
|
content=request.content,
|
|
|
|
|
metadata=request.metadata
|
|
|
|
|
)
|
|
|
|
|
if success:
|
|
|
|
|
return {"success": True, "updated": point_id}
|
|
|
|
|
else:
|
2026-04-15 15:43:18 +03:00
|
|
|
return JSONResponse(status_code=500, content={"success": False, "error": f"Failed to update point {point_id}"})
|
refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/memory/create")
|
|
|
|
|
async def create_memory_point(request: MemoryCreateRequest):
|
|
|
|
|
"""
|
|
|
|
|
Manually create a new memory (declarative fact or episodic memory).
|
|
|
|
|
|
|
|
|
|
For declarative facts, this allows you to teach Miku new knowledge.
|
|
|
|
|
For episodic memories, this allows you to inject conversation context.
|
|
|
|
|
"""
|
|
|
|
|
from utils.cat_client import cat_adapter
|
|
|
|
|
|
|
|
|
|
if request.collection not in ['declarative', 'episodic']:
|
2026-04-15 15:43:18 +03:00
|
|
|
return JSONResponse(status_code=400, content={"success": False, "error": "Collection must be 'declarative' or 'episodic'"})
|
refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:
core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
profile_picture.py (26), manual_send.py (3), servers.py (6),
figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
config.py (7), logging_config.py (9), voice.py (3), memory.py (10)
All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
|
|
|
|
|
|
|
|
# Create the memory point
|
|
|
|
|
result = await cat_adapter.create_memory_point(
|
|
|
|
|
collection=request.collection,
|
|
|
|
|
content=request.content,
|
|
|
|
|
user_id=request.user_id or "manual_admin",
|
|
|
|
|
source=request.source or "manual_web_ui",
|
|
|
|
|
metadata=request.metadata or {}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if result:
|
|
|
|
|
return {"success": True, "point_id": result, "collection": request.collection}
|
|
|
|
|
else:
|
2026-04-15 15:43:18 +03:00
|
|
|
return JSONResponse(status_code=500, content={"success": False, "error": "Failed to create memory point"})
|