feat: Jon Snow Phase 2 — FastAPI orchestrator with LiteLLM brain

OpenAI-compatible API at :8900. Intent classifier routes status queries
to FAST_MODEL (Ollama), task submissions to Plane, planning to SMART_MODEL.
Reads agent-os logs for status context. Phase 3: approval gate + execution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 13:06:20 +00:00
commit a25deeb8f4
9 changed files with 503 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
AGENT_NAMES = {"hodor", "bran", "varys", "sam", "raven", "qyburn", "citadel", "jon"}
STATUS_PHRASES = {
"status", "health", "running", "last run", "what did", "when did",
"show me", "how is", "is it", "is running", "did it run", "output",
"summary", "report", "check", "monitor", "alive", "up",
}
TASK_PHRASES = {
"create task", "add task", "add issue", "create issue", "log task",
"log this", "new task", "new issue", "add to plane", "add to backlog",
"plan", "schedule", "remind", "track", "todo", "to do",
}
RESEARCH_PHRASES = {
"research", "search", "find out", "look up", "what is", "explain",
"how does", "documentation", "docs",
}
def classify_intent(message: str) -> str:
msg = message.lower()
words = set(msg.split())
# Agent name + query word → status
if words & AGENT_NAMES:
if any(p in msg for p in STATUS_PHRASES) or words & {"status", "check", "output", "run"}:
return "status"
# Explicit task phrases → task
if any(p in msg for p in TASK_PHRASES):
return "task"
# Generic status signal words
if any(p in msg for p in STATUS_PHRASES) and words & AGENT_NAMES:
return "status"
# Status if asking purely about the agent ecosystem
if words & AGENT_NAMES and not any(p in msg for p in {"build", "implement", "create", "make"}):
return "status"
# Research intent → route to smart model
if any(p in msg for p in RESEARCH_PHRASES):
return "planning"
return "planning"
def extract_agent_name(message: str) -> str | None:
msg = message.lower()
for name in AGENT_NAMES:
if name in msg:
return name
return None
PROJECT_KEYWORDS = {
"bni": "BNI Scheduler",
"scheduler": "BNI Scheduler",
"monitor": "Monitoring",
"monitoring": "Monitoring",
"grafana": "Monitoring",
"maester": "Maester Reports",
"report": "Maester Reports",
"csf": "Maester Reports",
"nist": "Maester Reports",
"portal": "Nexum Portal",
"authelia": "Nexum Portal",
"nexum": "Nexum Portal",
}
def extract_project_name(message: str) -> str | None:
msg = message.lower()
for kw, project in PROJECT_KEYWORDS.items():
if kw in msg:
return project
return None