ruslanmv's picture
Deploy Matrix Context Console
ce45eb0 verified
Raw
History Blame Contribute Delete
6.42 kB
"""Matrix Context — frontend / control-plane server.
A self-contained, Hugging-Face-deployable launcher that serves:
* the static control-plane UI in ``frontend/app/`` (Overview, Inspector,
Builder, Memory, Experts, Routing, Benchmarks, Standard, Settings), and
* the live MoC Contract v1 API under ``/v1`` — by reusing the published
backend's ``RestService`` / ``dispatch``.
This is **additive and non-destructive**: it imports ``matrix_context`` and the
existing REST app; it does not modify them. A small demo memory set is seeded on
startup so the Space is useful immediately.
python frontend/server.py # local -> http://127.0.0.1:7860
PORT=7860 HOST=0.0.0.0 python ... # container / Hugging Face Space
"""
from __future__ import annotations
import json
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
from urllib.parse import parse_qs, urlparse
from matrix_context import ContextManager
from matrix_context.serve.rest.app import RestService, dispatch
APP_DIR = Path(__file__).parent / "app"
HOST = os.environ.get("HOST", "127.0.0.1")
PORT = int(os.environ.get("PORT", "7860"))
DB_PATH = os.environ.get("MATRIX_CONTEXT_PATH", ":memory:")
_CONTENT_TYPES = {".html": "text/html; charset=utf-8",
".js": "application/javascript; charset=utf-8",
".css": "text/css; charset=utf-8",
".svg": "image/svg+xml", ".json": "application/json",
".png": "image/png", ".ico": "image/x-icon"}
# Demo seed — a small, typed, multi-scope memory set so every view is populated.
SEED = [
("Decision: use SQLite as the default storage backend; vectors accelerate, SQL is the source of truth.",
"semantic", "project:matrix-context", 0.95, ["backend", "sqlite", "type:decision"]),
("Matrix Context exposes both a Python SDK and an MCP server so agents and operators share one memory plane.",
"semantic", "project:matrix-context", 0.9, ["mcp", "sdk", "type:decision"]),
("Policy: the agent must never store API keys, tokens, or PII in durable memory.",
"policy", "project:matrix-context", 0.95, ["security", "pii", "type:rule"]),
("Decision: defer Milvus support to v2; pgvector lands in v1.",
"episodic", "project:matrix-context", 0.8, ["milvus", "roadmap", "type:decision"]),
("The user prefers local-first tools over cloud services by default.",
"profile", "user:42", 0.9, ["preference", "type:preference"]),
("The user prefers concise, direct answers with code first.",
"profile", "user:42", 0.8, ["preference", "tone", "type:preference"]),
("The whitepaper reports that routed typed context cuts distractors at equal recall.",
"document", "project:matrix-context", 0.7, ["whitepaper", "type:document"]),
("Decision: ship the REST API and Context Console before the MCP server.",
"semantic", "project:acme", 0.85, ["rest", "console", "type:decision"]),
("Policy in project:acme — audit-log every write to profile or policy memory.",
"policy", "project:acme", 0.88, ["audit", "governance", "type:rule"]),
("In the last session we agreed the demo should run on Hugging Face Spaces.",
"episodic", "project:acme", 0.65, ["session", "demo", "type:episode"]),
]
def build_service() -> RestService:
mgr = ContextManager.create("matrix-context-demo", path=DB_PATH)
if not mgr.store.all_items():
for content, expert, scope, importance, tags in SEED:
mgr.remember(content, expert=expert, scope=scope,
importance=importance, tags=tags)
return RestService(mgr)
def _static(path: str):
"""Resolve a static asset under APP_DIR (path-traversal safe)."""
rel = path.lstrip("/") or "index.html"
target = (APP_DIR / rel).resolve()
if APP_DIR.resolve() not in target.parents and target != APP_DIR.resolve():
return None
if not target.is_file():
return None
ctype = _CONTENT_TYPES.get(target.suffix, "application/octet-stream")
return target.read_bytes(), ctype
def make_handler(service: RestService):
class Handler(BaseHTTPRequestHandler):
def log_message(self, *a):
pass
def _send_json(self, status, payload):
data = json.dumps(payload).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def _send_bytes(self, data, ctype, status=200):
self.send_response(status)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def _read_body(self):
n = int(self.headers.get("Content-Length") or 0)
if not n:
return {}
try:
obj = json.loads(self.rfile.read(n) or b"{}")
return obj if isinstance(obj, dict) else {}
except json.JSONDecodeError:
return {}
def do_GET(self):
p = urlparse(self.path)
if p.path.startswith("/v1"):
st, payload = dispatch(service, "GET", p.path, query=parse_qs(p.query))
self._send_json(st, payload)
return
asset = _static("index.html" if p.path == "/" else p.path)
if asset:
self._send_bytes(*asset)
else:
self._send_json(404, {"error": f"not found: {p.path}"})
def do_POST(self):
p = urlparse(self.path)
if p.path.startswith("/v1"):
st, payload = dispatch(service, "POST", p.path, body=self._read_body())
self._send_json(st, payload)
else:
self._send_json(404, {"error": f"not found: {p.path}"})
return Handler
def main() -> int:
service = build_service()
httpd = HTTPServer((HOST, PORT), make_handler(service))
n = len(service.m.store.all_items())
print(f"Matrix Context control plane on http://{HOST}:{PORT} ({n} seeded items)")
try:
httpd.serve_forever()
except KeyboardInterrupt:
httpd.server_close()
return 0
if __name__ == "__main__":
raise SystemExit(main())