338 lines
12 KiB
Python
338 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Golden-Weave Memory Extension Server
|
|
Proxies to lattice_observer (port 28820) and adds memory endpoints
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import requests
|
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
from socketserver import ThreadingMixIn
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, '/mnt/d/Resonance_Engine/beast-build')
|
|
|
|
try:
|
|
from golden_weave_memory import (
|
|
GoldenWeaveMemorySystem,
|
|
LocalFieldState,
|
|
PHI,
|
|
INV_PHI_SQUARED
|
|
)
|
|
MEMORY_SYSTEM_AVAILABLE = True
|
|
print("[EXTENSION] Golden-Weave Memory System loaded")
|
|
except ImportError as e:
|
|
print(f"[EXTENSION] Error loading memory system: {e}")
|
|
MEMORY_SYSTEM_AVAILABLE = False
|
|
sys.exit(1)
|
|
|
|
# Configuration
|
|
OBSERVER_URL = "http://127.0.0.1:28820"
|
|
EXTENSION_PORT = 28821
|
|
ATTRACTOR_DIR = "/mnt/d/Resonance_Engine/beast-build/attractors"
|
|
|
|
# Initialize memory system
|
|
memory_system = GoldenWeaveMemorySystem(
|
|
attractor_dir=ATTRACTOR_DIR,
|
|
grid_size=1024
|
|
)
|
|
|
|
print(f"[EXTENSION] {len(memory_system.list_attractors())} attractors loaded")
|
|
|
|
|
|
class MemoryExtensionHandler(BaseHTTPRequestHandler):
|
|
"""HTTP handler that proxies to observer and adds memory endpoints."""
|
|
|
|
server_version = "GoldenWeaveExtension/1.0"
|
|
|
|
def log_message(self, fmt, *args):
|
|
print(f"[EXTENSION] {fmt % args}")
|
|
|
|
def _send_json(self, data, status=200):
|
|
body = json.dumps(data).encode('utf-8')
|
|
self.send_response(status)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.send_header('Content-Length', str(len(body)))
|
|
self.send_header('Access-Control-Allow-Origin', '*')
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def do_OPTIONS(self):
|
|
self.send_response(204)
|
|
self.send_header('Access-Control-Allow-Origin', '*')
|
|
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
|
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
|
self.end_headers()
|
|
|
|
def do_GET(self):
|
|
# Check if this is a memory endpoint
|
|
if self.path.startswith('/query_local'):
|
|
self._handle_query_local()
|
|
elif self.path == '/list_attractors':
|
|
self._handle_list_attractors()
|
|
elif self.path.startswith('/recall_attractor'):
|
|
self._handle_recall_attractor()
|
|
elif self.path == '/status':
|
|
self._handle_status()
|
|
else:
|
|
# Proxy to observer
|
|
self._proxy_to_observer()
|
|
|
|
def do_POST(self):
|
|
# Check if this is a memory endpoint
|
|
if self.path == '/store_attractor':
|
|
self._handle_store_attractor()
|
|
else:
|
|
# Proxy to observer
|
|
self._proxy_to_observer_post()
|
|
|
|
def _handle_status(self):
|
|
"""Extension status + observer status."""
|
|
try:
|
|
observer_status = requests.get(f"{OBSERVER_URL}/status", timeout=5).json()
|
|
except:
|
|
observer_status = {"error": "observer unreachable"}
|
|
|
|
self._send_json({
|
|
"service": "Golden-Weave Memory Extension",
|
|
"port": EXTENSION_PORT,
|
|
"observer_url": OBSERVER_URL,
|
|
"observer_status": observer_status,
|
|
"memory_system": MEMORY_SYSTEM_AVAILABLE,
|
|
"attractors_stored": len(memory_system.list_attractors()),
|
|
"endpoints": {
|
|
"GET /query_local?x=512&y=512": "Query field at coordinates (mock data)",
|
|
"POST /store_attractor": "Store attractor definition",
|
|
"GET /list_attractors": "List all stored attractors",
|
|
"GET /recall_attractor?name=...": "Retrieve attractor params",
|
|
"GET /status": "This status page",
|
|
"/*": "Proxied to observer (port 28820)"
|
|
}
|
|
})
|
|
|
|
def _handle_query_local(self):
|
|
"""GET /query_local?x=512&y=512"""
|
|
# Parse parameters
|
|
x, y = 512, 512
|
|
if '?' in self.path:
|
|
params = self.path.split('?', 1)[1]
|
|
for part in params.split('&'):
|
|
if part.startswith('x='):
|
|
x = int(part[2:])
|
|
elif part.startswith('y='):
|
|
y = int(part[2:])
|
|
|
|
# Get observer telemetry for cycle number
|
|
try:
|
|
telemetry = requests.get(f"{OBSERVER_URL}/telemetry", timeout=5).json()
|
|
cycle = telemetry.get('cycle', 0)
|
|
coherence = telemetry.get('coherence', 0)
|
|
asymmetry = telemetry.get('asymmetry', 0)
|
|
except:
|
|
cycle = 0
|
|
coherence = 0
|
|
asymmetry = 0
|
|
|
|
# Create mock local state (in real implementation, would get from daemon)
|
|
# For now, return placeholder with actual telemetry
|
|
local_state = LocalFieldState(
|
|
x=x, y=y,
|
|
density=0.7 + 0.2 * (x % 10) / 10, # Mock density variation
|
|
stress_xx=-0.0001 + (x % 5) * 0.00001,
|
|
stress_yy=0.00005 + (y % 5) * 0.00001,
|
|
stress_xy=-0.00005,
|
|
vorticity=0.02 + (x + y) % 10 * 0.001,
|
|
velocity_x=0.1,
|
|
velocity_y=0.05,
|
|
timestamp="2026-03-22T13:00:00",
|
|
cycle=cycle
|
|
)
|
|
|
|
self._send_json({
|
|
"command": "query_local",
|
|
"x": x,
|
|
"y": y,
|
|
"density": local_state.density,
|
|
"stress_divergence": local_state.stress_divergence,
|
|
"stress_magnitude": local_state.stress_magnitude,
|
|
"vorticity": local_state.vorticity,
|
|
"velocity": [local_state.velocity_x, local_state.velocity_y],
|
|
"cycle": local_state.cycle,
|
|
"global_coherence": coherence,
|
|
"global_asymmetry": asymmetry,
|
|
"note": "Using mock field data (daemon integration pending)"
|
|
})
|
|
|
|
def _handle_store_attractor(self):
|
|
"""POST /store_attractor with JSON body."""
|
|
content_length = int(self.headers.get('Content-Length', 0))
|
|
if content_length > 10000:
|
|
self._send_json({'error': 'payload too large'}, 413)
|
|
return
|
|
|
|
body = self.rfile.read(content_length)
|
|
try:
|
|
data = json.loads(body)
|
|
except json.JSONDecodeError:
|
|
self._send_json({'error': 'invalid JSON'}, 400)
|
|
return
|
|
|
|
name = data.get('name', '').strip()
|
|
x = data.get('x', 512)
|
|
y = data.get('y', 512)
|
|
radius = data.get('radius', 20)
|
|
|
|
if not name:
|
|
self._send_json({'error': 'missing "name" field'}, 400)
|
|
return
|
|
|
|
# Get observer telemetry
|
|
try:
|
|
telemetry = requests.get(f"{OBSERVER_URL}/telemetry", timeout=5).json()
|
|
cycle = telemetry.get('cycle', 0)
|
|
except:
|
|
cycle = 0
|
|
|
|
# Create mock local state
|
|
local_state = LocalFieldState(
|
|
x=x, y=y,
|
|
density=data.get('density', 0.8),
|
|
stress_xx=data.get('stress_xx', -0.0001),
|
|
stress_yy=data.get('stress_yy', 0.00005),
|
|
stress_xy=data.get('stress_xy', -0.00005),
|
|
vorticity=data.get('vorticity', 0.02),
|
|
velocity_x=0.1,
|
|
velocity_y=0.05,
|
|
timestamp="2026-03-22T13:00:00",
|
|
cycle=cycle
|
|
)
|
|
|
|
injection_params = {
|
|
'amplitude': data.get('amplitude', 0.05),
|
|
'radius': data.get('injection_radius', 20),
|
|
'num_injections': data.get('num_injections', 5),
|
|
'omega': data.get('omega', 1.97)
|
|
}
|
|
|
|
try:
|
|
attractor = memory_system.store_attractor(
|
|
name=name,
|
|
center_x=x,
|
|
center_y=y,
|
|
radius=radius,
|
|
local_state=local_state,
|
|
injection_params=injection_params
|
|
)
|
|
|
|
self._send_json({
|
|
"command": "store_attractor",
|
|
"name": name,
|
|
"properties": memory_system.get_attractor_properties(name),
|
|
"status": "stored"
|
|
})
|
|
except Exception as e:
|
|
self._send_json({'error': str(e)}, 500)
|
|
|
|
def _handle_list_attractors(self):
|
|
"""GET /list_attractors"""
|
|
try:
|
|
attractors = memory_system.list_attractors()
|
|
properties = [memory_system.get_attractor_properties(name) for name in attractors]
|
|
|
|
self._send_json({
|
|
"command": "list_attractors",
|
|
"count": len(attractors),
|
|
"attractors": properties
|
|
})
|
|
except Exception as e:
|
|
self._send_json({'error': str(e)}, 500)
|
|
|
|
def _handle_recall_attractor(self):
|
|
"""GET /recall_attractor?name=..."""
|
|
name = ''
|
|
if '?' in self.path:
|
|
params = self.path.split('?', 1)[1]
|
|
for part in params.split('&'):
|
|
if part.startswith('name='):
|
|
name = part[5:]
|
|
|
|
if not name:
|
|
self._send_json({'error': 'missing "name" parameter'}, 400)
|
|
return
|
|
|
|
try:
|
|
attractor = memory_system.recall_attractor(name)
|
|
if attractor is None:
|
|
self._send_json({'error': f'attractor "{name}" not found'}, 404)
|
|
return
|
|
|
|
self._send_json({
|
|
"command": "recall_attractor",
|
|
"name": name,
|
|
"center": [attractor.center_x, attractor.center_y],
|
|
"injection_amplitude": attractor.injection_amplitude,
|
|
"injection_radius": attractor.injection_radius,
|
|
"num_injections": attractor.num_injections,
|
|
"omega": attractor.omega_at_creation,
|
|
"properties": memory_system.get_attractor_properties(name),
|
|
"status": "ready_for_injection"
|
|
})
|
|
except Exception as e:
|
|
self._send_json({'error': str(e)}, 500)
|
|
|
|
def _proxy_to_observer(self):
|
|
"""Proxy GET request to observer."""
|
|
try:
|
|
url = f"{OBSERVER_URL}{self.path}"
|
|
resp = requests.get(url, timeout=30)
|
|
self._send_proxy_response(resp)
|
|
except Exception as e:
|
|
self._send_json({'error': f'proxy failed: {str(e)}'}, 502)
|
|
|
|
def _proxy_to_observer_post(self):
|
|
"""Proxy POST request to observer."""
|
|
try:
|
|
content_length = int(self.headers.get('Content-Length', 0))
|
|
body = self.rfile.read(content_length) if content_length > 0 else b''
|
|
|
|
url = f"{OBSERVER_URL}{self.path}"
|
|
headers = {'Content-Type': 'application/json'}
|
|
resp = requests.post(url, data=body, headers=headers, timeout=360)
|
|
self._send_proxy_response(resp)
|
|
except Exception as e:
|
|
self._send_json({'error': f'proxy failed: {str(e)}'}, 502)
|
|
|
|
def _send_proxy_response(self, resp):
|
|
"""Send proxied response back to client."""
|
|
self.send_response(resp.status_code)
|
|
for header, value in resp.headers.items():
|
|
if header.lower() not in ('transfer-encoding', 'content-length'):
|
|
self.send_header(header, value)
|
|
self.send_header('Content-Length', str(len(resp.content)))
|
|
self.end_headers()
|
|
self.wfile.write(resp.content)
|
|
|
|
|
|
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
|
"""Handle requests in a separate thread."""
|
|
pass
|
|
|
|
|
|
def main():
|
|
server = ThreadedHTTPServer(('0.0.0.0', EXTENSION_PORT), MemoryExtensionHandler)
|
|
print(f"[EXTENSION] Server running on port {EXTENSION_PORT}")
|
|
print(f"[EXTENSION] Proxying to {OBSERVER_URL}")
|
|
print(f"[EXTENSION] Attractors stored in: {ATTRACTOR_DIR}")
|
|
print(f"[EXTENSION] Test: curl http://localhost:{EXTENSION_PORT}/status")
|
|
|
|
try:
|
|
server.serve_forever()
|
|
except KeyboardInterrupt:
|
|
print("\n[EXTENSION] Shutting down...")
|
|
server.shutdown()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|