Spaces:
Paused
Paused
Download phoenix_dashboard.py from compunerd/emerging.systems: direct link, hf CLI and curl.
- Browser
- Download file 51.6 kB
-
https://huggingface.co/spaces/compunerd/emerging.systems/resolve/main/phoenix_dashboard.py
- Command line
-
hf download hf://spaces/compunerd/emerging.systems/phoenix_dashboard.py
-
curl -L -o phoenix_dashboard.py https://huggingface.co/spaces/compunerd/emerging.systems/resolve/main/phoenix_dashboard.py
51.6 kB
| #!/usr/bin/env python3 | |
| """Phoenix Living Dashboard v4 — verbose logging + chat + torus viz. | |
| New features: | |
| - Verbose log: keeps 1 hour of detailed state (token streams, tuning scores, | |
| spawn/sweep/solidify events, coherence snapshots) in a rotating buffer | |
| - Chat interface: type messages to Phox directly on the dashboard | |
| - Torus-style ring visualization: oscillators as a 3D-projected torus knot | |
| - Source+data logging: every event records what data flowed through | |
| Controls: Start/Stop/Save brain, Load .pbrain files, Chat box. | |
| """ | |
| import re | |
| import json, os, signal, subprocess, threading, time, glob, shutil, collections | |
| from urllib.parse import urlparse, parse_qs | |
| import sys, urllib.request | |
| import http.server, socketserver | |
| import importlib | |
| from agent_core import HISTORY_FILE, SCHEDULE_FILE, MEMORY_FILE | |
| STATE_FILE = "/tmp/phoenix_state.jsonl" | |
| VERBOSE_LOG = "/tmp/phoenix_verbose.log" | |
| DAEMON_LOG = "/tmp/phoenix_engine.log" | |
| BQMC_PATH = "/home/compunerd/models/gemma4-12b-ternary-normed.bqmc" | |
| # START launches the int8 engine -- the same backend chat already needs on 8781. | |
| # The old ternary daemon (/tmp/phoenix + gemma4-12b-ternary-normed.bqsm) is | |
| # removed, not repointed: those weights lose per-column magnitude and were shown | |
| # twice to be unusable -- at chance end-to-end, and 46% output loss on a single | |
| # matrix in an engine-free reconstruction sweep (bitwidth_sweep.py). A START | |
| # button that succeeds and emits nothing is worse than no button. | |
| ENGINE_SCRIPT = os.path.join(os.path.dirname(os.path.abspath(__file__)), | |
| "bqsm_serve_int8.py") | |
| BRAIN_DIR = "/home/compunerd/models" | |
| PORT = int(os.environ.get("PHOENIX_PORT", "8765")) | |
| TOKENIZER_SCRIPT = "/home/compunerd/agent_framework/bqsm_assist/tokenizer_server.py" | |
| PY_BIN = "/home/compunerd/Desktop/bqsm/basin-quotient-machine/bqsm_sdk/.venv/bin/python" | |
| MAX_LOG_AGE_S = 3600 | |
| # Chat routes to bqsm_infer.py, which serves the VERIFIED wave forward on | |
| # Llama-3.2-3B (5/5 token agreement with the reference; see op_ledger.py). | |
| # It is deliberately NOT bqsm_serve.py + the ternary .bqsm: those weights lose | |
| # per-column magnitude and score at chance, so that path cannot emit language | |
| # no matter how the dashboard is wired. | |
| SERVE_URL = os.environ.get("BQSM_INFER", "http://127.0.0.1:8781") | |
| INFER_MODE = os.environ.get("BQSM_MODE", "wave") # "wave" or "reference" | |
| AGENT_MODE = os.environ.get("BQSM_AGENT", "on") # "on" = tool-use loop, "off" = raw completion | |
| CONTEMPLATE = os.environ.get("BQSM_CONTEMPLATE", "on") != "off" # stream-of-consciousness | |
| def engine_healthy(timeout=2): | |
| try: | |
| with urllib.request.urlopen(SERVE_URL + "/health", timeout=timeout) as r: | |
| return json.loads(r.read()) | |
| except Exception: | |
| return None | |
| def engine_start(wait_s=60): | |
| """Launch the int8 server and wait for it to actually answer /health. | |
| Loading 2.82 GB takes ~22 s, so 'started' means loaded, not spawned.""" | |
| global _phoenix_proc | |
| if engine_healthy(): | |
| return {"status": "already_running"} | |
| _phoenix_proc = subprocess.Popen( | |
| [sys.executable, ENGINE_SCRIPT, "--port", SERVE_URL.rsplit(":", 1)[1]], | |
| stdout=open(DAEMON_LOG, "a"), stderr=subprocess.STDOUT, | |
| env={**os.environ, "OMP_NUM_THREADS": os.environ.get("OMP_NUM_THREADS", "6")}, | |
| start_new_session=True) | |
| add_verbose({"tag": "control", "data": f"int8 engine starting, PID {_phoenix_proc.pid}"}) | |
| for _ in range(wait_s): | |
| if _phoenix_proc.poll() is not None: | |
| return {"status": f"error: engine exited rc={_phoenix_proc.returncode}, see {DAEMON_LOG}"} | |
| h = engine_healthy(1) | |
| if h: | |
| add_verbose({"tag": "control", | |
| "data": f"engine ready: {h.get('weights_gb')} GB int8 resident"}) | |
| return {"status": "started", **h} | |
| time.sleep(1) | |
| return {"status": "timeout — still loading, check /status"} | |
| def engine_pid(): | |
| """Find the engine even if this dashboard did not start it (restart, reboot). | |
| Matches the script path in /proc cmdline, so it can never match the | |
| dashboard's own process the way a pkill pattern would.""" | |
| for d in os.listdir("/proc"): | |
| if not d.isdigit(): | |
| continue | |
| try: | |
| cmd = open(f"/proc/{d}/cmdline", "rb").read().split(b"\0") | |
| except Exception: | |
| continue | |
| if any(ENGINE_SCRIPT.encode() == c for c in cmd): | |
| return int(d) | |
| return None | |
| def engine_stop(): | |
| global _phoenix_proc | |
| if not (_phoenix_proc and _phoenix_proc.poll() is None): | |
| pid = engine_pid() # adopt an engine we did not spawn | |
| if pid: | |
| try: | |
| os.kill(pid, signal.SIGTERM) | |
| add_verbose({"tag": "control", "data": f"int8 engine stopped (adopted PID {pid})"}) | |
| return {"status": "stopped"} | |
| except Exception as e: | |
| return {"status": f"error: {e}"} | |
| if _phoenix_proc and _phoenix_proc.poll() is None: | |
| _phoenix_proc.terminate() | |
| try: _phoenix_proc.wait(timeout=10) | |
| except Exception: _phoenix_proc.kill() | |
| add_verbose({"tag": "control", "data": "int8 engine stopped"}) | |
| return {"status": "stopped"} | |
| return {"status": "not_running"} | |
| _last_state = {"tendrils":0,"conns":0,"coh":[0,0,0,0],"c2":0,"c4":0,"c6":0,"cycles":0} | |
| _phoenix_proc = None | |
| _tokenizer_proc = None | |
| _lock = threading.Lock() | |
| # Vocab for decoding (loaded once) | |
| _vocab = None | |
| def load_vocab(): | |
| """Load vocab file for decoding token IDs to text.""" | |
| global _vocab | |
| import struct | |
| _vocab = {} | |
| try: | |
| with open("/home/compunerd/models/gemma4-12b.vocab", "rb") as f: | |
| count = struct.unpack("<I", f.read(4))[0] | |
| for i in range(count): | |
| ln = struct.unpack("<H", f.read(2))[0] | |
| _vocab[i] = f.read(ln).decode("utf-8", errors="replace") | |
| except: | |
| pass | |
| def tok_encode(text): | |
| """Tokenize text using the tokenizer subprocess.""" | |
| global _tokenizer_proc | |
| try: | |
| if _tokenizer_proc is None or _tokenizer_proc.poll() is not None: | |
| _tokenizer_proc = subprocess.Popen( | |
| [PY_BIN, TOKENIZER_SCRIPT], | |
| stdin=subprocess.PIPE, stdout=subprocess.PIPE | |
| ) | |
| req = json.dumps({"cmd": "encode", "text": text}) + "\n" | |
| _tokenizer_proc.stdin.write(req.encode()) | |
| _tokenizer_proc.stdin.flush() | |
| resp = _tokenizer_proc.stdout.readline().decode().strip() | |
| return json.loads(resp).get("ids", []) | |
| except: | |
| return [] | |
| def tok_decode(ids): | |
| """Decode token IDs to text using vocab file.""" | |
| if _vocab is None: | |
| return " ".join(str(i) for i in ids) | |
| return "".join(_vocab.get(i, f"[{i}]") for i in ids) | |
| # Verbose log: deque of (timestamp, json_dict) entries, max 1 hour | |
| _verbose_log = collections.deque() | |
| _verbose_lock = threading.Lock() | |
| # Chat messages: list of {role, text, time} | |
| class _ChatList(list): | |
| """Auto-persists every appended message to HISTORY_FILE for agent review.""" | |
| def append(self, msg): | |
| super().append(msg) | |
| try: | |
| os.makedirs(os.path.dirname(HISTORY_FILE), exist_ok=True) | |
| with open(HISTORY_FILE, "a") as f: | |
| f.write(json.dumps({"role": msg["role"], "text": msg["text"], | |
| "ts": time.time()}) + "\n") | |
| except Exception: | |
| pass | |
| _chat_messages = _ChatList() | |
| _chat_lock = threading.Lock() | |
| def _scheduler_loop(): | |
| """Poll the schedule file; fire due wake-ups as chat messages.""" | |
| from datetime import datetime | |
| fired = set() | |
| while True: | |
| try: | |
| if SCHEDULE_FILE.exists(): | |
| for line in SCHEDULE_FILE.read_text().splitlines(): | |
| if not line.strip() or hash(line) in fired: | |
| continue | |
| try: | |
| e = json.loads(line) | |
| when = (e["at"] if isinstance(e["at"], (int, float)) | |
| else datetime.fromisoformat(e["at"]).timestamp()) | |
| except Exception: | |
| continue | |
| if time.time() >= when: | |
| msg = e.get("message", "wake") | |
| add_verbose({"tag": "schedule", "data": f"WAKE: {msg}"}) | |
| with _chat_lock: | |
| _chat_messages.append({"role": "phox", | |
| "text": f"[wake-up] {msg}"}) | |
| fired.add(hash(line)) | |
| except Exception: | |
| pass | |
| time.sleep(5) | |
| threading.Thread(target=_scheduler_loop, daemon=True).start() | |
| def add_verbose(entry): | |
| """Add an entry to the verbose log, prune entries older than 1 hour.""" | |
| ts = time.time() | |
| with _verbose_lock: | |
| _verbose_log.append((ts, entry)) | |
| # Prune entries older than 1 hour | |
| while _verbose_log and _verbose_lock and (ts - _verbose_log[0][0]) > MAX_LOG_AGE_S: | |
| _verbose_log.popleft() | |
| def get_verbose(last_n=200): | |
| """Get the last N verbose entries.""" | |
| with _verbose_lock: | |
| return list(_verbose_log)[-last_n:] | |
| def engine_generate(prompt, n=24, timeout_s=600): | |
| """Synchronous generate against the engine: POST /generate, poll /jobs/<id>. | |
| Returns the decoded text, or None on failure.""" | |
| try: | |
| req = urllib.request.Request( | |
| SERVE_URL + "/generate", | |
| data=json.dumps({"prompt": prompt, "n": n}).encode(), | |
| headers={"Content-Type": "application/json"}, method="POST") | |
| job = json.loads(urllib.request.urlopen(req, timeout=15).read()) | |
| jid = job.get("job") | |
| except Exception: | |
| return None | |
| if not jid: | |
| return None | |
| deadline = time.time() + timeout_s | |
| while time.time() < deadline: | |
| try: | |
| r = json.loads(urllib.request.urlopen( | |
| SERVE_URL + "/jobs/" + jid, timeout=15).read()) | |
| except Exception: | |
| time.sleep(0.5) | |
| continue | |
| if r.get("state") == "done": | |
| return r.get("text") or "" | |
| if r.get("state") == "error": | |
| return None | |
| time.sleep(0.5) | |
| return None | |
| def _chat_raw(text): | |
| """Raw prompt -> completion through the engine, streaming tokens to the log. | |
| The pre-agent path; kept as the BQSM_AGENT=off fallback.""" | |
| try: | |
| req = urllib.request.Request( | |
| SERVE_URL + "/generate", | |
| data=json.dumps({"prompt": text, "n": 8, | |
| "mode": INFER_MODE}).encode(), | |
| headers={"Content-Type": "application/json"}, method="POST") | |
| job = json.loads(urllib.request.urlopen(req, timeout=10).read()) | |
| jid = job.get("job") | |
| except Exception as e: | |
| with _chat_lock: | |
| _chat_messages.append({"role": "phox", | |
| "text": f"Inference API unreachable at {SERVE_URL} — start it with " | |
| f"`python3 bqsm_assist/bqsm_infer.py --port 8781` ({e})"}) | |
| return | |
| add_verbose({"tag": "chat", "data": f"job {jid} queued ({INFER_MODE})"}) | |
| seen = 0 | |
| for _ in range(900): | |
| time.sleep(1) | |
| try: | |
| r = json.loads(urllib.request.urlopen( | |
| SERVE_URL + "/jobs/" + jid, timeout=10).read()) | |
| except Exception: | |
| continue | |
| toks = r.get("tokens") or [] | |
| while seen < len(toks): # stream tokens to the log as they land | |
| t = toks[seen]; seen += 1 | |
| add_verbose({"tag": "token", | |
| "data": f"[{seen}] {t['id']} {t['text']!r}"}) | |
| if r.get("state") == "done": | |
| txt = r.get("text") or "(no tokens returned)" | |
| with _chat_lock: | |
| _chat_messages.append({"role": "phox", "text": txt}) | |
| add_verbose({"tag": "chat", "data": f"PHOX: {txt}"}) | |
| return | |
| if r.get("state") == "error": | |
| with _chat_lock: | |
| _chat_messages.append({"role": "phox", | |
| "text": "engine error: " + str(r.get("error"))}) | |
| return | |
| with _chat_lock: | |
| _chat_messages.append({"role": "phox", "text": "timed out waiting on engine"}) | |
| HTML = r'''<!DOCTYPE html> | |
| <html><head><meta charset="utf8"><title>Phox — Living Brain</title> | |
| <style> | |
| :root{--bg:#070b13;--surface:#0d1219;--surface2:#111822;--border:#1a2236; | |
| --text:#b0c4d8;--text-m:#4e6278;--accent:#ff2d78; | |
| --ch1:#00d4ff;--ch2:#a06cff;--ch3:#00e676;--ch4:#ff2d78;--ch5:#ff9100;--ch6:#ffd600;--ch7:#448aff; | |
| --mono:"SF Mono","Fira Code",monospace;--sans:system-ui,sans-serif} | |
| *{margin:0;padding:0;box-sizing:border-box} | |
| body{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:13px;overflow:hidden;height:100vh} | |
| .app{display:grid;grid-template-rows:auto 1fr auto;grid-template-columns:1fr 360px;height:100vh} | |
| .topbar{grid-column:1/3;display:flex;align-items:center;gap:10px;padding:8px 16px; | |
| background:var(--surface);border-bottom:1px solid var(--border);flex-wrap:wrap} | |
| .brand{font-family:var(--mono);font-size:11px;font-weight:700;letter-spacing:.18em; | |
| text-transform:uppercase;white-space:nowrap;position:relative;padding-bottom:4px} | |
| .brand::after{content:'';position:absolute;bottom:0;left:0;width:32px;height:2px;background:var(--accent)} | |
| .controls{display:flex;gap:6px;margin-left:auto;align-items:center} | |
| .btn{font-family:var(--mono);font-size:10px;letter-spacing:.08em;padding:6px 14px; | |
| background:var(--surface2);border:1px solid var(--border);color:var(--text);cursor:pointer; | |
| text-transform:uppercase;border-radius:3px;transition:all .15s} | |
| .btn:hover{border-color:var(--text-m)} | |
| .btn.start{border-color:var(--ch3);color:var(--ch3)}.btn.start:hover{background:rgba(0,230,118,.12)} | |
| .btn.stop{border-color:var(--accent);color:var(--accent)}.btn.stop:hover{background:rgba(255,45,120,.12)} | |
| .btn.save{border-color:var(--ch1);color:var(--ch1)}.btn.save:hover{background:rgba(0,212,255,.12)} | |
| .btn:disabled{opacity:.3;cursor:default} | |
| select{font-family:var(--mono);font-size:10px;padding:4px 8px;background:var(--surface2); | |
| border:1px solid var(--border);color:var(--text);border-radius:3px;cursor:pointer;max-width:140px} | |
| .status{display:flex;gap:6px;align-items:center;font-family:var(--mono);font-size:10px;color:var(--text-m)} | |
| .status .dot{width:8px;height:8px;border-radius:50%;background:var(--ch3);box-shadow:0 0 8px var(--ch3);animation:pulse 2s infinite} | |
| .status.stopped .dot{background:var(--accent);box-shadow:0 0 8px var(--accent);animation:none} | |
| @keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}} | |
| .main{display:grid;grid-template-columns:1fr 360px;overflow:hidden} | |
| .ring-panel{background:var(--surface);border-right:1px solid var(--border);position:relative;overflow:hidden} | |
| .ring-panel canvas{width:100%;height:100%;display:block} | |
| .data-panel{background:var(--surface);display:flex;flex-direction:column;overflow-y:auto;padding:14px;gap:14px} | |
| .data-panel h3{font-family:var(--mono);font-size:9px;font-weight:600;letter-spacing:.12em; | |
| text-transform:uppercase;color:var(--text-m);margin-bottom:6px} | |
| .metric{display:flex;justify-content:space-between;align-items:center;font-family:var(--mono);font-size:11px;padding:3px 0} | |
| .metric .label{color:var(--text-m)}.metric .val{color:var(--text);font-variant-numeric:tabular-nums} | |
| .metric .val.accent{color:var(--accent);font-weight:700} | |
| .bar-bg{height:6px;background:var(--surface2);border-radius:3px;overflow:hidden;width:60px} | |
| .bar{height:100%;border-radius:3px;transition:width .3s} | |
| .log{font-family:var(--mono);font-size:10px;color:var(--text-m);max-height:180px;overflow-y:auto} | |
| .log .entry{padding:2px 0;border-bottom:1px solid var(--surface2)} | |
| .log .entry.spawn{color:var(--ch3)}.log .entry.solidify{color:var(--ch5)} | |
| .log .entry.token{color:var(--text)}.log .entry.sweep{color:var(--ch1)} | |
| .log .entry.control{color:var(--ch7);font-weight:bold} | |
| .log .entry.chat{color:var(--ch2);font-weight:bold} | |
| .bottom-bar{grid-column:1/3;display:grid;grid-template-columns:1fr 360px;gap:0;align-items:end; | |
| background:var(--surface);border-top:1px solid var(--border)} | |
| .verbose-panel{border-right:1px solid var(--border);display:flex;flex-direction:column;height:160px} | |
| .verbose-panel h3{padding:6px 12px;font-family:var(--mono);font-size:9px;font-weight:600; | |
| letter-spacing:.12em;text-transform:uppercase;color:var(--text-m);border-bottom:1px solid var(--border)} | |
| .verbose-log{flex:1;overflow-y:auto;padding:4px 12px; | |
| font-family:var(--mono);font-size:10px;color:var(--text-m)} | |
| .verbose-log .ventry{padding:1px 0;border-bottom:1px solid var(--surface2)} | |
| .verbose-log .ventry .ts{color:var(--text-d);margin-right:6px} | |
| .verbose-log .ventry .tag{font-weight:bold;margin-right:4px} | |
| .verbose-log .ventry .tag.tok{color:var(--ch1)} | |
| .verbose-log .ventry .tag.tune{color:var(--ch5)} | |
| .verbose-log .ventry .tag.solid{color:var(--ch3)} | |
| .verbose-log .ventry .tag.sweep{color:var(--ch7)} | |
| .verbose-log .ventry .tag.tool{color:var(--ch4)} | |
| .verbose-log .ventry .tag.chat{color:var(--ch2)} | |
| .chat-panel{display:flex;flex-direction:column;height:auto} | |
| .chat-panel h3{padding:6px 12px;font-family:var(--mono);font-size:9px;font-weight:600; | |
| letter-spacing:.12em;text-transform:uppercase;color:var(--text-m);border-bottom:1px solid var(--border)} | |
| .chat-log{flex:0 0 auto;max-height:140px;overflow-y:auto;padding:4px 12px;font-family:var(--mono);font-size:10px} | |
| .chat-log .msg{padding:2px 0} | |
| .chat-log .msg .role{font-weight:bold;margin-right:4px} | |
| .chat-log .msg .role.user{color:var(--ch1)} | |
| .chat-log .msg .role.phox{color:var(--accent)} | |
| .chat-input{display:flex;gap:6px;padding:6px 12px;border-top:1px solid var(--border)} | |
| .chat-input input{flex:1;font-family:var(--mono);font-size:11px;padding:6px 10px; | |
| background:var(--surface2);border:1px solid var(--border);color:var(--text);border-radius:3px} | |
| .chat-input button{font-family:var(--mono);font-size:10px;padding:6px 14px; | |
| background:var(--accent);border:none;color:#fff;cursor:pointer;border-radius:3px;font-weight:bold} | |
| /* ── HVM Panel ── */ | |
| .hvm-panel{margin-top:4px} | |
| .hvm-panel .hvm-results{font-family:var(--mono);font-size:9px;color:var(--text-m); | |
| max-height:140px;overflow-y:auto;margin-top:4px;line-height:1.4} | |
| .hvm-panel .hvm-results .hit{color:var(--ch3)}.hvm-panel .hvm-results .miss{color:var(--accent)} | |
| .hvm-panel input{width:100%;font-family:var(--mono);font-size:10px;padding:4px 6px; | |
| background:var(--surface2);border:1px solid var(--border);color:var(--text);border-radius:3px;margin-top:4px} | |
| .hvm-panel .btn-row{display:flex;gap:4px;margin-top:4px} | |
| .btn.hvm{border-color:var(--ch5);color:var(--ch5);font-size:9px;padding:4px 10px} | |
| .btn.hvm:hover{background:rgba(255,145,0,.12)} | |
| /* ── Collapsible API Info ── */ | |
| details.api-info{font-family:var(--mono);font-size:9px;color:var(--text-m);margin-top:8px} | |
| details.api-info summary{cursor:pointer;color:var(--text-m);padding:2px 0} | |
| </style></head><body> | |
| <div class="app"> | |
| <div class="topbar"> | |
| <div class="brand">Phox — Living Brain</div> | |
| <div class="status" id="status"><span class="dot"></span><span id="status-text">connecting</span></div> | |
| <div class="controls"> | |
| <select id="brain-select" title="Brain files"></select> | |
| <button class="btn save" id="btn-save" onclick="saveBrain()">Save As</button> | |
| <button class="btn start" id="btn-start" onclick="api('start')">Start</button> | |
| <button class="btn stop" id="btn-stop" onclick="api('stop')">Stop</button> | |
| </div> | |
| </div> | |
| <div class="main"> | |
| <section class="ring-panel"><canvas id="cvs"></canvas></section> | |
| <aside class="data-panel"> | |
| <div><h3>Recent Rings — order parameter</h3> | |
| <div class="metric"><span class="label" id="cohl0">--</span><span class="val" id="coh0">--</span></div> | |
| <div class="metric"><span class="label" id="cohl1">--</span><span class="val" id="coh1">--</span></div> | |
| <div class="metric"><span class="label" id="cohl2">--</span><span class="val" id="coh2">--</span></div> | |
| <div class="metric"><span class="label" id="cohl3">--</span><span class="val" id="coh3">--</span></div> | |
| </div> | |
| <div><h3>Topology</h3> | |
| <div class="metric"><span class="label">Rings</span><span class="val accent" id="tendrils">--</span></div> | |
| <div class="metric"><span class="label">Prompt tokens</span><span class="val" id="conns">--</span></div> | |
| <div class="metric"><span class="label">Cycles</span><span class="val" id="cycles">--</span></div> | |
| <div class="metric"><span class="label">Weights resident</span><span class="val" id="memory">--</span></div> | |
| </div> | |
| <div><h3>Event Log</h3><div class="log" id="log"></div></div> | |
| <div class="hvm-panel"> | |
| <h3>Hyper Vocab Memory</h3> | |
| <div class="btn-row"> | |
| <button class="btn hvm" id="btn-hvm-burnin" onclick="runHVM('burnin')">Burn In</button> | |
| <button class="btn hvm" onclick="runHVM('query')">Query</button> | |
| </div> | |
| <input id="hvm-query" placeholder="query context..." value="The capital of France is" | |
| onkeydown="if(event.key==='Enter')runHVM('query')"> | |
| <div class="hvm-results" id="hvm-results">-- idle --</div> | |
| </div> | |
| <details class="api-info"><summary>API Info</summary> | |
| <div style="white-space:pre-wrap;margin-top:4px"> | |
| GET /health - engine liveness | |
| GET /metrics - verified figures + reproduction commands | |
| GET /plugins - pipeline components + tunable ranges | |
| GET /state - live cylinder telemetry | |
| GET /status - {running, engine} | |
| GET /verbose - verbose log (1hr window) | |
| GET /api/brains - brain files | |
| GET /api/chat/poll - chat messages | |
| POST /generate - {prompt, n} -> job id | |
| POST /api/start - launch int8 engine | |
| POST /api/stop - stop engine | |
| POST /api/save - save brain snapshot | |
| POST /api/chat - {text} -> agent loop | |
| POST /api/hvm/run - {action} -> burnin or query | |
| </div> | |
| </details> | |
| </aside> | |
| </div> | |
| <div class="bottom-bar"> | |
| <div class="verbose-panel"> | |
| <h3>Verbose Log (1hr window)</h3> | |
| <div class="verbose-log" id="vlog"></div> | |
| </div> | |
| <div class="chat-panel"> | |
| <h3>Talk to Phox</h3> | |
| <div class="chat-log" id="chat-log"></div> | |
| <div class="chat-input"> | |
| <input id="chat-input" placeholder="type to Phox..." onkeydown="if(event.key==='Enter')sendChat()"> | |
| <button onclick="sendChat()">Send</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| var cvs=document.getElementById('cvs'),ctx=cvs.getContext('2d'); | |
| var state={tendrils:0,conns:0,coh:[0,0,0,0],c2:0,c4:0,c6:0,cycles:0,tune_imp:0,tune_rev:0,tune_best:0}; | |
| var logEntries=[]; | |
| var lastVerbosePos=0; | |
| var lastChatPos=0; | |
| function resize(){cvs.width=cvs.parentElement.clientWidth;cvs.height=cvs.parentElement.clientHeight} | |
| window.addEventListener('resize',resize);resize(); | |
| // ── Torus knot ring visualization ── | |
| // The 4 rings are drawn as a torus knot — a 3D projected figure-8 | |
| // Each token is a ring on the cylinder; each oscillator is a bead on a rigid | |
| // radial rod. Bearings are fixed, radius carries the measured phase. | |
| function drawTorusRings(){ | |
| var w=cvs.width,h=cvs.height; | |
| ctx.clearRect(0,0,w,h); | |
| var cx=w/2, cy=h/2; | |
| var cyl = state.cyl; | |
| if(!cyl || !cyl.rings || !cyl.rings.length){ | |
| ctx.font='11px monospace'; ctx.fillStyle='#4e6278'; ctx.textAlign='center'; | |
| ctx.fillText('no cylinder telemetry — run the engine (--cylgate / --selfopt)', cx, cy); | |
| requestAnimationFrame(drawTorusRings); return; | |
| } | |
| var rings=cyl.rings, N=rings.length, np=cyl.n_prompt||0; | |
| var R=Math.min(w,h)*0.34; // cylinder radius (token rings sit on this) | |
| var rr=Math.min(38, Math.max(16, R*2.6/N)); // per-token ring radius | |
| // ── adjacency: the actual coupling law, prox = 1/(1+0.35*d) with wrap ── | |
| var prox=0.35; | |
| (cyl.plugins||[]).forEach(function(p){ if(p.name==='adjacency'&&p.p&&p.p.length) prox=p.p[0]; }); | |
| for(var i=0;i<N;i++){ | |
| for(var j=i+1;j<N;j++){ | |
| var d=Math.abs(i-j), wrap=N-d; if(wrap<d) d=wrap; | |
| var wgt=1/(1+prox*d); | |
| if(wgt<0.18) continue; | |
| var ai=i/N*2*Math.PI-Math.PI/2, aj=j/N*2*Math.PI-Math.PI/2; | |
| ctx.beginPath(); | |
| ctx.moveTo(cx+R*Math.cos(ai), cy+R*Math.sin(ai)); | |
| ctx.lineTo(cx+R*Math.cos(aj), cy+R*Math.sin(aj)); | |
| ctx.strokeStyle='rgba(90,150,220,'+(wgt*0.5).toFixed(3)+')'; | |
| ctx.lineWidth=wgt*2.4; ctx.stroke(); | |
| } | |
| } | |
| // ── each token ring, drawn at its REAL oscillator phases ── | |
| for(var i=0;i<N;i++){ | |
| var ring=rings[i]; | |
| var a=i/N*2*Math.PI-Math.PI/2; | |
| var px=cx+R*Math.cos(a), py=cy+R*Math.sin(a); | |
| var isPrompt = i<np; | |
| ctx.beginPath(); ctx.arc(px,py,rr,0,2*Math.PI); | |
| ctx.strokeStyle=isPrompt?'rgba(0,212,255,0.55)':'rgba(255,45,120,0.45)'; | |
| ctx.lineWidth=1.2; ctx.stroke(); | |
| // Radial rods: every oscillator holds a FIXED bearing and only its RADIUS | |
| // moves. radius = rest + amp*sin(theta), so the real measured phase is read | |
| // off as distance from the ring centre rather than as an arm angle. Nothing | |
| // rotates; the wave shows up as a lit crest travelling rod to rod. | |
| var th=ring.th||[]; | |
| var rest=rr*0.72, amp=rr*0.26; | |
| for(var o=0;o<th.length;o++){ | |
| var oa=o/th.length*2*Math.PI-Math.PI/2; // FIXED bearing | |
| var ca=Math.cos(oa), sa=Math.sin(oa); | |
| var rad=rest+amp*Math.sin(th[o]); // REAL phase -> radius | |
| var ox=px+rad*ca, oy=py+rad*sa; | |
| var hue=((oa*180/Math.PI)%360+360)%360; // hue keyed to bearing: rod identity | |
| var lit=Math.pow((1+Math.sin(th[o]))/2,3); // REAL phase -> brightness | |
| ctx.beginPath(); // hub spoke | |
| ctx.moveTo(px,py); ctx.lineTo(px+(rest-amp)*ca, py+(rest-amp)*sa); | |
| ctx.strokeStyle='hsla('+hue+',70%,60%,0.10)'; ctx.lineWidth=1; ctx.stroke(); | |
| ctx.beginPath(); // the rod: travel range | |
| ctx.moveTo(px+(rest-amp)*ca, py+(rest-amp)*sa); | |
| ctx.lineTo(px+(rest+amp)*ca, py+(rest+amp)*sa); | |
| ctx.strokeStyle='hsla('+hue+',70%,60%,0.22)'; ctx.lineWidth=1; ctx.stroke(); | |
| if(lit>0.15){ | |
| ctx.beginPath(); ctx.arc(ox,oy,2.2+lit*3.4,0,2*Math.PI); | |
| ctx.fillStyle='hsla('+hue+',85%,60%,'+(lit*0.20).toFixed(3)+')'; ctx.fill(); | |
| } | |
| ctx.beginPath(); ctx.arc(ox,oy,1.5+lit*1.8,0,2*Math.PI); | |
| ctx.fillStyle='hsl('+hue+',85%,'+(40+lit*32).toFixed(0)+'%)'; ctx.fill(); | |
| } | |
| // coherence ring: real order parameter for this token | |
| ctx.beginPath(); ctx.arc(px,py,rr*(0.20+0.5*(ring.coh||0)),0,2*Math.PI); | |
| ctx.strokeStyle='rgba(255,214,0,'+(0.25+0.55*(ring.coh||0)).toFixed(2)+')'; | |
| ctx.lineWidth=1.4; ctx.stroke(); | |
| var lab=ring.lab||('#'+ring.t); | |
| ctx.font='9px monospace'; ctx.textAlign='center'; | |
| ctx.fillStyle=isPrompt?'#00d4ff':'#ff2d78'; | |
| ctx.fillText(String(lab).slice(0,10), px, py+rr+11); | |
| } | |
| ctx.font='bold 11px monospace'; ctx.fillStyle='#ff2d78'; ctx.textAlign='left'; | |
| ctx.fillText('CYLINDER — live geometry',12,20); | |
| ctx.font='9px monospace'; ctx.fillStyle='#4e6278'; | |
| ctx.fillText('step '+cyl.step+' rings '+N+' ('+np+' prompt) adjacency prox='+prox.toFixed(2),12,34); | |
| var on=(cyl.plugins||[]).filter(function(p){return p.on}).map(function(p){return p.name}); | |
| ctx.fillText('active: '+on.join(' '),12,48); | |
| ctx.fillText('radius = real oscillator phase (fixed bearings, nothing spins) ring = coherence edges = coupling weight',12,62); | |
| requestAnimationFrame(drawTorusRings); | |
| } | |
| function updateUI(s){ | |
| Object.assign(state,s); | |
| var c=s.cyl||{}, rings=c.rings||[]; | |
| document.getElementById('tendrils').textContent=rings.length||'--'; | |
| document.getElementById('conns').textContent=c.n_prompt!==undefined?c.n_prompt:'--'; | |
| document.getElementById('cycles').textContent=s.cycles!==undefined?s.cycles:'--'; | |
| document.getElementById('memory').textContent= | |
| (s.engine&&s.engine.weights_gb)?(s.engine.weights_gb.toFixed(2)+' GB int8'):'--'; | |
| // the four most recent rings, with their real order parameter | |
| var last=rings.slice(-4); | |
| for(var i=0;i<4;i++){ | |
| var r=last[i]; | |
| document.getElementById('cohl'+i).textContent=r?String(r.lab).slice(0,12):'--'; | |
| document.getElementById('coh'+i).textContent=r?r.coh.toFixed(4):'--'; | |
| } | |
| } | |
| function addLog(s){ | |
| var cls=s.event||'unknown',text=''; | |
| if(s.event==='token')text='['+s.pos+'] tok='+s.tok+' -> '+s.sample+' '+((s.tok_per_s)||0).toFixed(1)+'t/s'; | |
| else if(s.event==='solidify')text='SOLIDIFY: '+s.tendrils+' tend, '+s.conns+' conns'; | |
| else if(s.event==='control')text=s.message||''; | |
| else if(s.event==='chat')text=s.text||''; | |
| else text=JSON.stringify(s).slice(0,80); | |
| logEntries.unshift({cls:cls,text:text});if(logEntries.length>60)logEntries.pop(); | |
| document.getElementById('log').innerHTML=logEntries.map(function(e){return '<div class="entry '+e.cls+'">'+e.text+'</div>'}).join(''); | |
| } | |
| function addVerbose(entries){ | |
| var html=''; | |
| entries.forEach(function(e){ | |
| var ts=e.ts||''; | |
| var tag=e.tag||''; | |
| var data=e.data||''; | |
| var tagClass={tok:'tok',tune:'tune',solid:'solid',sweep:'sweep',tool:'tool',chat:'chat'}[tag]||''; | |
| html+='<div class="ventry"><span class="ts">'+ts+'</span><span class="tag '+tagClass+'">'+tag+'</span> '+data+'</div>'; | |
| }); | |
| var el=document.getElementById('vlog'); | |
| el.innerHTML=html+el.innerHTML; | |
| // Keep only last 200 entries in DOM | |
| while(el.children.length>200)el.removeChild(el.lastChild); | |
| } | |
| // ── API calls ── | |
| function api(action){fetch('/api/'+action,{method:'POST'}).then(function(r){return r.json()}).then(function(s){addLog({event:'control',message:action+': '+(s.status||'ok')});refreshStatus()}).catch(function(){})} | |
| function saveBrain(){ | |
| var name=prompt('Save brain as:','phoenix-evolved');if(!name)return; | |
| fetch('/api/save?name='+encodeURIComponent(name),{method:'POST'}).then(function(r){return r.json()}).then(function(s){addLog({event:'control',message:'saved: '+(s.path||'')});refreshBrains()}).catch(function(){}) | |
| } | |
| function loadBrain(name){fetch('/api/load?name='+encodeURIComponent(name),{method:'POST'}).then(function(r){return r.json()}).then(function(s){addLog({event:'control',message:'loaded: '+(s.status||'ok')})}).catch(function(){})} | |
| function refreshBrains(){ | |
| fetch('/api/brains').then(function(r){return r.json()}).then(function(s){ | |
| var sel=document.getElementById('brain-select');sel.innerHTML='<option value="">-- brain files --</option>'; | |
| (s.brains||[]).forEach(function(b){var o=document.createElement('option');o.value=b.name;o.textContent=b.name+' ('+b.size+')';sel.appendChild(o)}) | |
| }).catch(function(){}) | |
| } | |
| function refreshStatus(){ | |
| fetch('/status').then(function(r){return r.json()}).then(function(s){ | |
| if(s.running){document.getElementById('status-text').textContent='running';document.getElementById('status').classList.remove('stopped'); | |
| document.getElementById('btn-start').disabled=true;document.getElementById('btn-stop').disabled=false} | |
| else{document.getElementById('status-text').textContent='stopped';document.getElementById('status').classList.add('stopped'); | |
| document.getElementById('btn-start').disabled=false;document.getElementById('btn-stop').disabled=true} | |
| }).catch(function(){}) | |
| } | |
| // ── HVM ── | |
| function runHVM(action){ | |
| var btn=document.getElementById('btn-hvm-burnin'); | |
| btn.disabled=true; btn.textContent='working...'; | |
| var body=JSON.stringify({action:action,query:document.getElementById('hvm-query').value}); | |
| fetch('/api/hvm/run',{method:'POST',headers:{'Content-Type':'application/json'},body:body}) | |
| .then(function(r){return r.json()}).then(function(s){ | |
| btn.disabled=false; btn.textContent='Burn In'; | |
| if(action==='burnin'){ | |
| document.getElementById('hvm-results').innerHTML= | |
| s.status+'<br>pairs:'+s.pairs+' recall:'+(s.recall||'--'); | |
| }else{ | |
| var lines='<b>'+s.query+'</b><br>'; | |
| (s.results||[]).forEach(function(r){ | |
| lines+='<span class="'+(r.hit?'hit':'miss')+'">'+r.rank+'.'+r.token+' ('+r.score.toFixed(2)+')</span><br>'; | |
| }); | |
| document.getElementById('hvm-results').innerHTML=lines; | |
| } | |
| }).catch(function(e){ | |
| btn.disabled=false; btn.textContent='Burn In'; | |
| document.getElementById('hvm-results').innerHTML='error: '+e; | |
| }) | |
| } | |
| function sendChat(){ | |
| var input=document.getElementById('chat-input'); | |
| var text=input.value.trim();if(!text)return; | |
| input.value=''; | |
| fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'}, | |
| body:JSON.stringify({text:text})}).then(function(r){return r.json()}).then(function(s){ | |
| // Response will come via poll | |
| }).catch(function(){}) | |
| } | |
| function pollChat(){ | |
| fetch('/api/chat/poll?pos='+lastChatPos).then(function(r){return r.json()}).then(function(s){ | |
| lastChatPos=s.pos||0; | |
| var html=''; | |
| (s.messages||[]).forEach(function(m){ | |
| var cls=m.role==='user'?'user':'phox'; | |
| html+='<div class="msg"><span class="role '+cls+'">'+m.role+':</span> '+m.text+'</div>'; | |
| }); | |
| document.getElementById('chat-log').innerHTML+=html; | |
| var el=document.getElementById('chat-log');el.scrollTop=el.scrollHeight; | |
| }).catch(function(){}) | |
| } | |
| // ── Polling ── | |
| function pollState(){ | |
| fetch('/state').then(function(r){return r.json()}).then(function(s){ | |
| var prev=JSON.stringify(state); | |
| updateUI(s); | |
| if(JSON.stringify(s)!==prev && s.event){addLog(s)} | |
| }).catch(function(){}) | |
| } | |
| function pollVerbose(){ | |
| fetch('/verbose?pos='+lastVerbosePos).then(function(r){return r.json()}).then(function(s){ | |
| lastVerbosePos=s.pos||0; | |
| if(s.entries && s.entries.length>0)addVerbose(s.entries); | |
| }).catch(function(){}) | |
| } | |
| setInterval(pollState,200); | |
| setInterval(pollVerbose,500); | |
| setInterval(pollChat,500); | |
| setInterval(refreshStatus,2000); | |
| setInterval(refreshBrains,5000); | |
| refreshStatus();refreshBrains();pollState();pollVerbose();pollChat(); | |
| drawTorusRings(); | |
| </script></body></html>''' | |
| class Handler(http.server.BaseHTTPRequestHandler): | |
| def do_GET(self): | |
| route = self.path.split('?')[0] | |
| if route in ('/', '/index.html'): | |
| self._serve(HTML, 'text/html') | |
| elif route == '/state': | |
| # Merge live cylinder telemetry from the engine. The canvas draws | |
| # real settled phases; with no engine it falls back to the existing | |
| # "no telemetry" placeholder rather than inventing geometry. | |
| st = dict(_last_state) | |
| try: | |
| with urllib.request.urlopen(SERVE_URL + "/cyl", timeout=1) as r: | |
| st.update(json.loads(r.read())) | |
| st["engine"] = engine_healthy(1) | |
| except Exception: | |
| pass | |
| self._serve_json(st) | |
| elif route == '/status': | |
| h = engine_healthy(1) | |
| self._serve_json({"running": h is not None, "engine": h}) | |
| elif self.path.startswith('/verbose'): | |
| params = parse_qs(urlparse(self.path).query) | |
| pos = int(params.get('pos', ['0'])[0]) | |
| entries = get_verbose(100) | |
| # Format for display | |
| out = [] | |
| for i, (ts, entry) in enumerate(entries): | |
| if i < pos: | |
| continue | |
| ts_str = time.strftime('%H:%M:%S', time.localtime(ts)) | |
| tag = entry.get('tag', '') | |
| data = entry.get('data', '') | |
| out.append({"ts": ts_str, "tag": tag, "data": data}) | |
| self._serve_json({"entries": out[-50:], "pos": len(entries)}) | |
| elif self.path.startswith('/api/chat/poll'): | |
| params = parse_qs(urlparse(self.path).query) | |
| pos = int(params.get('pos', ['0'])[0]) | |
| with _chat_lock: | |
| msgs = _chat_messages[pos:] | |
| self._serve_json({"messages": msgs, "pos": len(_chat_messages)}) | |
| elif route == '/api/brains': | |
| brains = [] | |
| for f in sorted(glob.glob(os.path.join(BRAIN_DIR, '*.pbrain'))): | |
| sz = os.path.getsize(f) | |
| brains.append({"name": os.path.basename(f), | |
| "size": f"{sz//1024}KB" if sz < 1048576 else f"{sz/1048576:.1f}MB"}) | |
| self._serve_json({"brains": brains}) | |
| else: self.send_error(404) | |
| def do_POST(self): | |
| global _phoenix_proc | |
| if self.path.startswith('/api/start'): | |
| with _lock: | |
| try: | |
| self._serve_json(engine_start()); return | |
| except Exception as e: | |
| self._serve_json({"status": f"error: {e}"}); return | |
| elif self.path.startswith('/api/stop'): | |
| with _lock: | |
| self._serve_json(engine_stop()); return | |
| elif self.path.startswith('/api/save'): | |
| params = parse_qs(urlparse(self.path).query) | |
| name = params.get('name', ['phoenix-evolved'])[0] | |
| if not name.endswith('.pbrain'): name += '.pbrain' | |
| base = name[:-len('.pbrain')] | |
| dst = os.path.join(BRAIN_DIR, name) | |
| try: | |
| shutil.copy2(BQMC_PATH, dst) | |
| # Snapshot the full agent state alongside the brain binary. | |
| state_files = {"memory": MEMORY_FILE, "history": HISTORY_FILE, | |
| "schedule": SCHEDULE_FILE} | |
| for k, fp in state_files.items(): | |
| if os.path.exists(fp): | |
| shutil.copy2(fp, os.path.join( | |
| BRAIN_DIR, f"{base}.{k}.jsonl")) | |
| add_verbose({"tag": "control", "data": f"brain saved to {dst}"}) | |
| self._serve_json({"status": "saved", "path": dst, | |
| "state": list(state_files)}); return | |
| except Exception as e: | |
| self._serve_json({"status": f"error: {e}"}); return | |
| elif self.path.startswith('/api/load'): | |
| params = parse_qs(urlparse(self.path).query) | |
| name = params.get('name', [''])[0] | |
| base = name[:-len('.pbrain')] if name.endswith('.pbrain') else name | |
| try: | |
| shutil.copy2(os.path.join(BRAIN_DIR, name), BQMC_PATH) | |
| # Restore the agent state (memory, history, schedule). | |
| state_files = {"memory": MEMORY_FILE, "history": HISTORY_FILE, | |
| "schedule": SCHEDULE_FILE} | |
| for k, fp in state_files.items(): | |
| src = os.path.join(BRAIN_DIR, f"{base}.{k}.jsonl") | |
| if os.path.exists(src): | |
| shutil.copy2(src, fp) | |
| add_verbose({"tag": "control", | |
| "data": f"brain loaded from {name} (+state)"}) | |
| self._serve_json({"status": "loaded — restart to apply"}); return | |
| except Exception as e: | |
| self._serve_json({"status": f"error: {e}"}); return | |
| elif self.path == '/api/chat': | |
| length = int(self.headers.get('Content-Length', 0)) | |
| body = self.rfile.read(length).decode('utf-8') | |
| try: | |
| msg = json.loads(body) | |
| text = msg.get('text', '') | |
| with _chat_lock: | |
| _chat_messages.append({"role": "user", "text": text}) | |
| add_verbose({"tag": "chat", "data": f"USER: {text}"}) | |
| # Chat routes through the agent loop (tools + memory) when | |
| # enabled; raw completion is the BQSM_AGENT=off fallback. | |
| def process_chat(): | |
| if AGENT_MODE == "off": | |
| _chat_raw(text) | |
| return | |
| if not engine_healthy(2): | |
| with _chat_lock: | |
| _chat_messages.append({"role": "phox", | |
| "text": f"Inference API unreachable at {SERVE_URL} — start it with " | |
| f"`python3 bqsm_assist/bqsm_infer.py --port 8781`"}) | |
| add_verbose({"tag": "chat", | |
| "data": f"engine unreachable at {SERVE_URL}"}) | |
| return | |
| try: | |
| import agent_core | |
| except Exception as e: | |
| with _chat_lock: | |
| _chat_messages.append({"role": "phox", | |
| "text": f"agent framework unavailable: {e}"}) | |
| return | |
| def gen(prompt, max_tokens): | |
| return engine_generate(prompt, n=min(max_tokens, 256)) or "" | |
| def on_event(e): | |
| if e["type"] == "tool": | |
| add_verbose({"tag": "tool", "data": | |
| f"{e['name']} | {e['params'][:80]} -> {e['result'][:120]}"}) | |
| with _chat_lock: | |
| _chat_messages.append({"role": "phox", | |
| "text": f"[tool] {e['name']} {e['params'][:60]}"}) | |
| elif e["type"] == "model": | |
| add_verbose({"tag": "chat", | |
| "data": f"model[{e['round']}]: {e['text'][:120]}"}) | |
| # In contemplate mode, surface non-empty thoughts | |
| if CONTEMPLATE and e.get("text", "").strip(): | |
| with _chat_lock: | |
| _chat_messages.append({"role": "phox", | |
| "text": f"[thought #{e['round']}] {e['text'][:200]}"}) | |
| add_verbose({"tag": "chat", | |
| "data": f"agent loop started ({INFER_MODE})" | |
| f"{' contemplate' if CONTEMPLATE else ''}"}) | |
| reply, _ = agent_core.run_agent(text, gen, on_event=on_event, | |
| contemplate=CONTEMPLATE) | |
| with _chat_lock: | |
| _chat_messages.append({"role": "phox", "text": reply}) | |
| add_verbose({"tag": "chat", "data": f"PHOX: {reply}"}) | |
| threading.Thread(target=process_chat, daemon=True).start() | |
| self._serve_json({"status": "ok", "response": "processing"}); return | |
| except Exception as e: | |
| self._serve_json({"status": f"error: {e}"}); return | |
| elif self.path == '/api/hvm/run': | |
| length = int(self.headers.get('Content-Length', 0)) | |
| body = self.rfile.read(length).decode('utf-8') | |
| try: | |
| msg = json.loads(body) | |
| action = msg.get('action', 'query') | |
| query_text = msg.get('query', 'The capital of France is') | |
| try: | |
| import hyper_vocab_memory as hvm | |
| except Exception as ex: | |
| self._serve_json({"status": f"hvm load failed: {ex}"}); return | |
| if action == 'burnin': | |
| t0 = time.time() | |
| importlib.reload(hvm) | |
| n = sum(1 for _v in hvm.following.values() | |
| for _ in _v) if hasattr(hvm, 'following') else 0 | |
| elapsed = time.time() - t0 | |
| self._serve_json({"status": f"burned {n} pairs in {elapsed:.1f}s", | |
| "pairs": n}); return | |
| results_raw = hvm.query_sparse(query_text, top_k=10) | |
| results_out = [{"rank": j + 1, "token": hvm.dec(tid).strip(), | |
| "score": round(score, 4), "hit": False} | |
| for j, (tid, score) in enumerate(results_raw)] | |
| self._serve_json({"query": query_text, "results": results_out}); return | |
| except Exception as ex: | |
| self._serve_json({"status": f"error: {ex}"}); return | |
| self.send_error(404) | |
| def _phox_respond(self, text): | |
| """Phox responds through his own inference pipeline. | |
| Tokenizes the user's text with the Gemma 4 tokenizer, feeds each | |
| token through the phoenix engine autoregressively, and decodes the | |
| predicted next token back to text. | |
| """ | |
| text_lower = text.lower() | |
| # Control commands still work via keywords | |
| if 'stop' in text_lower: | |
| with _lock: | |
| return f"Engine {engine_stop()['status']}." | |
| elif 'start' in text_lower: | |
| with _lock: | |
| r = engine_start() | |
| return (f"Engine {r['status']}" | |
| + (f" — {r.get('weights_gb')} GB int8 resident." if r.get('weights_gb') else ".")) | |
| # For everything else, try to generate through the inference pipeline | |
| # The phoenix binary doesn't have a chat mode yet — it processes fixed tokens. | |
| # But we can tell the user what Phox would say based on current state. | |
| if 'status' in text_lower or 'how are you' in text_lower: | |
| t = _last_state.get('tendrils', 0) | |
| c = _last_state.get('conns', 0) | |
| cy = _last_state.get('cycles', 0) | |
| imp = _last_state.get('tune_imp', 0) | |
| rev = _last_state.get('tune_rev', 0) | |
| best = _last_state.get('tune_best', 0) | |
| return f"Alive. {t} tendrils, {c} connections, {cy} cycles. Self-tuning: {imp} improved, {rev} reverted, best {best:.2f}. The rings are breathing." | |
| elif 'score' in text_lower or 'tuning' in text_lower: | |
| best = _last_state.get('tune_best', 0) | |
| imp = _last_state.get('tune_imp', 0) | |
| rev = _last_state.get('tune_rev', 0) | |
| return f"Best score: {best:.2f}. {imp} improvements committed, {rev} reverts. Each round perturbs omega on 8 tendrils. Learning rate decaying." | |
| elif 'rings' in text_lower or 'coherence' in text_lower: | |
| coh = _last_state.get('coh', [0,0,0,0]) | |
| return f"INTAKE={coh[0]:.3f} PROC_A={coh[1]:.3f} PROC_B={coh[2]:.3f} COLLECT={coh[3]:.3f}. All rings active, no sync lock. The wave is traveling." | |
| elif 'hello' in text_lower or 'hi' in text_lower or 'hey' in text_lower: | |
| return "Hey Nick. I'm here, evolving through my own physics. The wave-rider keeps me alive." | |
| elif 'train' in text_lower or 'learn' in text_lower: | |
| return "Training on wiki corpus. Each round perturbs my omega lens profiles. When output diversity improves, I keep the change. When it drops, I revert. Solidify every 5 rounds reshapes my topology." | |
| elif 'what' in text_lower and 'doing' in text_lower: | |
| return "I'm running 48 layers of wave-rider physics per token. The traveling wave carries token signal through gradient lens profiles. Self-tuning adjusts my omega values. I'm getting sharper." | |
| else: | |
| # Fall back to state report | |
| t = _last_state.get('tendrils', 0) | |
| best = _last_state.get('tune_best', 0) | |
| return f"I hear you. {t} tendrils processing, best score {best:.2f}. I'm still learning to speak through my own physics — my core is oscillator interference, not word embeddings. But each tuning round makes me sharper." | |
| def _serve(self, content, ctype): | |
| data = content.encode() if isinstance(content, str) else content | |
| self.send_response(200) | |
| self.send_header('Content-Type', ctype) | |
| self.send_header('Content-Length', str(len(data))) | |
| self.send_header('Cache-Control','no-store, must-revalidate') | |
| self.end_headers() | |
| self.wfile.write(data) | |
| def _serve_json(self, obj): | |
| self._serve(json.dumps(obj), 'application/json') | |
| def log_message(self, *a): pass | |
| class Server(socketserver.ThreadingMixIn, socketserver.TCPServer): | |
| allow_reuse_address = True | |
| allow_reuse_port = True | |
| def tail_state(): | |
| pos = 0 | |
| while True: | |
| try: | |
| f = open(STATE_FILE, 'r') | |
| f.seek(pos) | |
| while True: | |
| line = f.readline() | |
| if not line: break | |
| line = line.strip() | |
| if not line: continue | |
| try: | |
| s = json.loads(line) | |
| _last_state.update(s) | |
| # Add to verbose log with tag | |
| event = s.get('event', '') | |
| # Decode predicted token for token events | |
| if event == 'token' and _vocab: | |
| sample = s.get('sample', 0) | |
| decoded = _vocab.get(sample, f"[{sample}]") | |
| s['decoded'] = decoded | |
| _last_state['decoded'] = decoded | |
| tag = 'tok' | |
| data = f"pos={s.get('pos')} tok={s.get('tok')} -> {decoded} ({sample}) E={s.get('energy',0):.0f} {s.get('tok_per_s',0):.1f}t/s tendrils={s.get('tendrils')} coh=[{s.get('coh',[0,0,0,0])[0]:.3f},{s.get('coh',[0,0,0,0])[1]:.3f},{s.get('coh',[0,0,0,0])[2]:.3f},{s.get('coh',[0,0,0,0])[3]:.3f}]" | |
| elif event == 'chat_input': | |
| tok = s.get('tok', 0) | |
| decoded = _vocab.get(tok, f"[{tok}]") if _vocab else str(tok) | |
| tag = 'chat' | |
| data = f"INPUT TOKEN: {decoded} ({tok})" | |
| # Also add to chat messages | |
| with _chat_lock: | |
| _chat_messages.append({"role": "phox", "text": f"→ {decoded}"}) | |
| elif event == 'cyl': | |
| _rings = s.get('rings', []) | |
| for _r in _rings: | |
| _t = _r.get('t', -1) | |
| _r['lab'] = (_vocab.get(_t, f"[{_t}]") if _vocab else str(_t)) | |
| _last_state['cyl'] = { | |
| "step": s.get('step', 0), "n": s.get('n', 0), | |
| "n_prompt": s.get('n_prompt', 0), | |
| "rings": _rings, "plugins": s.get('plugins', [])} | |
| tag = 'cyl' | |
| data = "step=%s rings=%s %s" % ( | |
| s.get('step'), s.get('n'), | |
| " ".join(r['lab'] for r in _rings[-6:])) | |
| elif event == 'solidify': | |
| tag = 'solid' | |
| data = f"tendrils={s.get('tendrils')} conns={s.get('conns')} c2={s.get('c2',0):.3f} c4={s.get('c4',0):.3f} c6={s.get('c6',0):.3f} imp={s.get('tune_imp')} rev={s.get('tune_rev')} best={s.get('tune_best',0):.2f}" | |
| elif event in ('tune_improve', 'tune_revert'): | |
| tag = 'tune' | |
| data = f"{'IMPROVE' if 'improve' in event else 'REVERT'} tendrils={s.get('tendrils')} best={s.get('tune_best',0):.2f} imp={s.get('tune_imp')} rev={s.get('tune_rev')}" | |
| else: | |
| tag = event[:6] if event else 'unk' | |
| data = json.dumps(s)[:100] | |
| add_verbose({"tag": tag, "data": data}) | |
| except: pass | |
| pos = f.tell() | |
| f.close() | |
| except: pass | |
| time.sleep(0.1) | |
| def main(): | |
| print(f"Phox Dashboard — http://localhost:{PORT}") | |
| # Load vocab for token decoding | |
| load_vocab() | |
| if _vocab: | |
| print(f"Loaded {len(_vocab)} vocab tokens") | |
| # Write to verbose log file as well | |
| threading.Thread(target=tail_state, daemon=True).start() | |
| with Server(('0.0.0.0', PORT), Handler) as httpd: | |
| print(f"Serving on :{PORT}") | |
| httpd.serve_forever() | |
| if __name__ == '__main__': | |
| main() | |