""" emma_server.py — HTTP-wrapper rundt Emma-agenten. Start: cd /home/chris_christiansen/OSVauco && python -m emma.emma_server Port: 8765 Endepunkter: POST /ask {"prompt": "...", "history": [...]} → {"response": "..."} GET /health → {"status": "ok", "model": "...", "memory_patterns": N} GET /stats → Flynn-statistikk Modell-prioritet (iht. llm-stack.md): 1. qwen2.5-coder:7b (kode/repo-arbeid, tools) 2. qwen2.5:7b (generell, tools) 3. gemma3:4b (fallback, ingen tools) Regler (iht. emma_identity.py + HANDOFF.md): - Emma rapporterer kun til Chris Christiansen - Guardrails kan ikke deaktiveres - Aldri deploy uten eksplisitt GO fra Chris - Gitea er primær Git """ import sys import json import logging from pathlib import Path from http.server import BaseHTTPRequestHandler, HTTPServer sys.path.insert(0, str(Path(__file__).parent.parent)) from emma.emma_backend_ollama import OllamaBackend from emma.emma_resonance_persistent import PersistentMorphicMemory from emma.emma_mcot import EmmaMCoTAgent from emma.emma_guardrails import EmmaGuardrails from emma.emma_flynn import FlynnTracker logging.basicConfig( level=logging.INFO, format="%(asctime)s [emma-server] %(levelname)s %(message)s", ) log = logging.getLogger("emma-server") # Modell-prioritet iht. llm-stack.md DEFAULT_MODEL = "qwen2.5-coder:7b" DEFAULT_EMBED = "qwen2.5:3b" # brukes til morfisk resonans-embedding PORT = 8765 def _build_agent(model: str = DEFAULT_MODEL) -> tuple: llm = OllamaBackend(model=model, embed_model=DEFAULT_EMBED) memory = PersistentMorphicMemory() guardrails = EmmaGuardrails() agent = EmmaMCoTAgent(llm=llm, memory=memory) return agent, guardrails, memory # Bygg agent ved oppstart log.info(f"Laster Emma med modell={DEFAULT_MODEL} ...") _agent, _guardrails, _memory = _build_agent() log.info(f"Emma klar | {len(_memory)} morfiske m\u00f8nstre lastet") class EmmaHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): # noqa: A002 log.info(f"{self.address_string()} {format % args}") def _send_json(self, code: int, data: dict): body = json.dumps(data, ensure_ascii=False).encode() self.send_response(code) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def _read_body(self) -> dict: length = int(self.headers.get("Content-Length", 0)) raw = self.rfile.read(length) if length else b"{}" try: return json.loads(raw) except json.JSONDecodeError: return {} def do_GET(self): if self.path == "/health": self._send_json(200, { "status": "ok", "model": DEFAULT_MODEL, "memory_patterns": len(_memory), "agent": "EmmaMCoTAgent", }) elif self.path == "/stats": stats = FlynnTracker().summary() or {} self._send_json(200, stats) else: self._send_json(404, {"error": "not found"}) def do_POST(self): if self.path != "/ask": self._send_json(404, {"error": "not found"}) return body = self._read_body() prompt = (body.get("prompt") or "").strip() history = body.get("history") or [] if not prompt: self._send_json(400, {"error": "prompt er p\u00e5krevd"}) return # Guardrail-sjekk ok, reason = _guardrails.can_execute(prompt) if not ok: log.warning(f"Guardrail blokkerte: {reason}") self._send_json(403, {"error": reason, "blocked": True}) return log.info(f"Sp\u00f8rsm\u00e5l: {prompt[:80]}") try: response = _agent.run(prompt, history) self._send_json(200, {"response": response, "model": DEFAULT_MODEL}) except Exception as e: log.error(f"Agent-feil: {e}") self._send_json(500, {"error": str(e)}) def main(): server = HTTPServer(("0.0.0.0", PORT), EmmaHandler) log.info(f"Emma HTTP-server lytter p\u00e5 port {PORT}") try: server.serve_forever() except KeyboardInterrupt: log.info("Emma server stoppet") server.server_close() if __name__ == "__main__": main()