Files
will.anderson b77f537dc6 fix cross-repo path deps; remove el compiler from neuron-lang/
- neuron-runtime, neuron-store, neuron-migrate: fix path deps
  (products/engram/ → foundation/engram/engrams/, products/el/ → foundation/el/engrams/)
- neuron-rs workspace: fix axon-events/axon-protocol paths
  (../../../platform/ → ../../platform/, paths were off by one level)
- neuron-lang/compiler/ removed (el self-hosting compiler now lives in foundation/el/el-compiler/)
2026-04-29 03:27:39 -05:00

140 lines
5.7 KiB
EmacsLisp

// main-user.el Neuron daemon entry point. User build.
// Written in Engram.
//
// Principal identity is a compile-time constant NOT read from config.
// This file is the user build. The developer build is main.el.
// Built with: el build --manifest el-user.toml
//
// Responsibilities:
// 1. Write PID file to ~/.neuron-user/data/daemon.pid
// 2. Start API/proxy server on :7750 (blocking)
// - Proxies /axon/, /api/ routes to neuronrs at :7770
// - Health check at /health
//
// The handle_request function is called by http_serve for every request.
import "daemon_config.el"
import "proxy.el"
import "health.el"
import "loop.el"
import "plugins/host.el"
// Principal identity (baked in at compile time)
// This literal is compiled into the bytecode. For prod builds it is sealed
// inside AES-256-GCM cannot be changed without the deployment key.
let principal: String = "user"
// Load operational config
let cfg: String = load_config()
let axon_base: String = config_api_url(cfg)
let token: String = config_api_token(cfg)
let ui_dir: String = config_ui_dir(cfg)
let data_dir: String = config_data_dir(cfg)
let port: Int = config_port(cfg)
state_set("neuron_principal", principal)
// Write PID file
let pid: Int = getpid()
let pid_str: String = int_to_str(pid)
fs_mkdir(data_dir)
let pid_path: String = data_dir + "/daemon.pid"
fs_write(pid_path, pid_str)
println(color_bold("Neuron daemon") + " — pid " + pid_str + "" + principal)
println(" API → http://localhost:" + int_to_str(port))
println(" Axon → " + axon_base)
println(" Data → " + data_dir)
println("")
// Request handler (API server on :7750)
//
// Called by http_serve for every incoming request.
// Must be named exactly "handle_request" http_serve looks it up by that name.
fn handle_request(method: String, path: String, body: String) -> String {
// Health check
if str_eq(path, "/health") {
return health_response()
}
// Axon tool dispatch proxy to neuronrs
if str_starts_with(path, "/axon/") {
return proxy_request(axon_base, method, path, body, token)
}
// Intelligence REST API proxy to neuronrs
if str_starts_with(path, "/api/memories") {
return proxy_request(axon_base, method, path, body, token)
}
if str_starts_with(path, "/api/knowledge") {
return proxy_request(axon_base, method, path, body, token)
}
if str_starts_with(path, "/api/backlog") {
return proxy_request(axon_base, method, path, body, token)
}
if str_starts_with(path, "/api/contexts") {
return proxy_request(axon_base, method, path, body, token)
}
if str_starts_with(path, "/api/ise") {
return proxy_request(axon_base, method, path, body, token)
}
// Plugin status
if str_eq(path, "/plugin/status") {
return host_status()
}
// Runtime loop control
if str_eq(path, "/loop/status") {
return loop_status_json()
}
if str_eq(path, "/loop/signal") {
let sig: String = json_get(body, "signal")
if str_eq(sig, "") {
return "{\"error\":\"missing signal\"}"
}
state_set("loop_signal", sig)
return "{\"ok\":true,\"signal\":\"" + sig + "\"}"
}
if str_eq(path, "/loop/tier") {
let new_tier: String = json_get(body, "tier")
let new_min: String = json_get(body, "min_tier")
if !str_eq(new_min, "") {
state_set("loop_min_tier", new_min)
}
if !str_eq(new_tier, "") {
state_set("loop_override_tier", new_tier)
}
return "{\"ok\":true,\"tier\":\"" + new_tier + "\",\"min_tier\":\"" + new_min + "\"}"
}
return not_found_response(path)
}
// Initialise plugin host
host_on_startup()
// Initialise loop state
state_set("loop_tier", "watching")
state_set("loop_min_tier", "resting")
state_set("loop_ticks", "0")
state_set("loop_bell_fires", "0")
state_set("loop_tier_changes", "0")
state_set("loop_idle_ticks", "0")
state_set("loop_signal", "")
state_set("loop_override_tier", "")
state_set("loop_last_tick_idle", "0")
// Spawn the heartbeat thread
spawn_thread("loop_main")
// Start API server (blocking)
http_serve(port)