Implement Sophia MVP scaffold (stages 0–3 + topology pivot)
Six-crate Rust workspace (core/sim/llm/store/server/bin) backing a Three.js + WebGL frontend. Live at http://127.0.0.1:7777 via `cargo run`. Sim - Event-driven scheduler with min-heap, per-engram tick, staggered Spawn events (40 ms apart) so each engram's flight is visually readable. - Solid-donut torus topology (replaces original spherical density-driven shell, see Topology Pivot in docs/system-analysis.md). Configurable major/minor radii in config.toml; live `POST /api/galaxy/:id/resize`. - Physics: Verlet integration + friction; in-hole pull + galactic spin (CCW around +z) for spiral-ejection ejection from the donut centre; soft tube boundary with velocity-reflecting wall. - Cosine-weighted gravity (kiddo k-NN within radius 25, threshold 0.50) and synapse formation (threshold 0.62) gated to inside-the-tube only. - LM Studio integration via OpenAI-compatible REST: batched embeddings, optional Bearer auth, semaphore-bounded parallel ops per §13.5. Server - axum HTTP + WebSocket. Routes: /healthz, /api/galaxy CRUD, /seed, /ingest, /resize, /engrams/:id, /ws/galaxy/:id/events. - Binary 12-byte-aligned position frames at ~20 Hz; JSON for sparse events (Hello, EngramCreated, SynapseCreated, TorusUpdated). - Layered config: config.toml (defaults) + config.local.toml (secrets, gitignored) merged on startup. Frontend - Vite + vanilla TypeScript + three.js 0.169. - Engrams render as additive bloom-friendly point sprites with a per-engram hash-driven hue rotation and breathing pulse. - Comet-style velocity-aligned trails; additive ribbon synapses whose endpoints track engram positions every frame. - UnrealBloomPass + ACES tone-mapping for the linked-particles look. - HUD shows torus dims, engram + synapse counts, LM Studio status; controls for seed, ingest, and live torus resize. Docs - README replaced with docs/IDEA.md; system-analysis.md updated with the topology pivot decisions and Galaxy Ejection refinement notes (the implementation plan lives in ~/.claude/plans, gitignored). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
24
crates/sophia-bin/Cargo.toml
Normal file
24
crates/sophia-bin/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "sophia-bin"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "sophia"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
sophia-core = { workspace = true }
|
||||
sophia-server = { workspace = true }
|
||||
sophia-sim = { workspace = true }
|
||||
sophia-llm = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
133
crates/sophia-bin/src/main.rs
Normal file
133
crates/sophia-bin/src/main.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
//! Sophia binary. Reads `config.toml`, initializes tracing, starts the server.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
use sophia_core::GalaxyShape;
|
||||
use sophia_llm::{LmStudioClient, LmStudioConfig, TokenCapsAll};
|
||||
use sophia_server::{serve, ServerConfig};
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Config {
|
||||
server: ServerSection,
|
||||
lm_studio: LmStudioConfig,
|
||||
#[allow(dead_code)] // wired in to LLM call sites in Stages 4+
|
||||
token_caps: TokenCapsAll,
|
||||
#[serde(default)]
|
||||
galaxy_defaults: GalaxyDefaultsSection,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ServerSection {
|
||||
host: String,
|
||||
port: u16,
|
||||
static_dir: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct GalaxyDefaultsSection {
|
||||
major_radius: f32,
|
||||
minor_radius: f32,
|
||||
}
|
||||
|
||||
impl Default for GalaxyDefaultsSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
major_radius: GalaxyShape::DEFAULT_MAJOR,
|
||||
minor_radius: GalaxyShape::DEFAULT_MINOR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_tracing() {
|
||||
tracing_subscriber::registry()
|
||||
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info,sophia=debug")))
|
||||
.with(fmt::layer())
|
||||
.init();
|
||||
}
|
||||
|
||||
/// Recursively merge `local` into `base`. Tables are deep-merged; scalars
|
||||
/// and arrays are overwritten by `local`. Used to layer `config.local.toml`
|
||||
/// (gitignored, secrets) on top of `config.toml` (versioned defaults).
|
||||
fn merge_toml(base: &mut toml::Value, local: toml::Value) {
|
||||
match (base, local) {
|
||||
(toml::Value::Table(b), toml::Value::Table(l)) => {
|
||||
for (k, v) in l {
|
||||
match b.get_mut(&k) {
|
||||
Some(existing) => merge_toml(existing, v),
|
||||
None => {
|
||||
b.insert(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(slot, other) => *slot = other,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_config() -> Result<Config> {
|
||||
let base_path = "config.toml";
|
||||
if !Path::new(base_path).exists() {
|
||||
anyhow::bail!("no config.toml found in cwd");
|
||||
}
|
||||
let base_text = std::fs::read_to_string(base_path)
|
||||
.with_context(|| format!("reading {base_path}"))?;
|
||||
let mut merged: toml::Value = toml::from_str(&base_text)
|
||||
.with_context(|| format!("parsing {base_path}"))?;
|
||||
tracing::info!("loaded config from {base_path}");
|
||||
|
||||
let local_path = "config.local.toml";
|
||||
if Path::new(local_path).exists() {
|
||||
let local_text = std::fs::read_to_string(local_path)
|
||||
.with_context(|| format!("reading {local_path}"))?;
|
||||
let local_value: toml::Value = toml::from_str(&local_text)
|
||||
.with_context(|| format!("parsing {local_path}"))?;
|
||||
merge_toml(&mut merged, local_value);
|
||||
tracing::info!("merged overrides from {local_path}");
|
||||
}
|
||||
|
||||
let cfg: Config = merged.try_into().context("deserializing merged config")?;
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
init_tracing();
|
||||
let cfg = load_config()?;
|
||||
|
||||
let server_cfg = ServerConfig {
|
||||
host: cfg.server.host,
|
||||
port: cfg.server.port,
|
||||
static_dir: PathBuf::from(cfg.server.static_dir),
|
||||
};
|
||||
|
||||
let llm = LmStudioClient::new(cfg.lm_studio);
|
||||
// Best-effort startup ping — failure logs but doesn't block boot, so the
|
||||
// server starts even if LM Studio isn't running yet (the user can launch
|
||||
// it after, and `/healthz` will report current status).
|
||||
match llm.list_models().await {
|
||||
Ok(models) => tracing::info!("LM Studio reachable; {} model(s) loaded", models.len()),
|
||||
Err(e) => tracing::warn!("LM Studio unreachable at startup: {e}"),
|
||||
}
|
||||
|
||||
let default_shape = GalaxyShape {
|
||||
major_radius: cfg.galaxy_defaults.major_radius,
|
||||
minor_radius: cfg.galaxy_defaults.minor_radius,
|
||||
};
|
||||
if let Err(msg) = default_shape.validate() {
|
||||
anyhow::bail!("invalid [galaxy_defaults] in config: {msg}");
|
||||
}
|
||||
tracing::info!(
|
||||
"galaxy defaults: major_radius={} minor_radius={}",
|
||||
default_shape.major_radius,
|
||||
default_shape.minor_radius
|
||||
);
|
||||
|
||||
let sim = sophia_sim::spawn_sim(default_shape);
|
||||
let info = sim.create_galaxy("default".to_string()).await?;
|
||||
tracing::info!("default galaxy ready: id={:?} name={}", info.id, info.name);
|
||||
|
||||
serve(server_cfg, sim, llm).await
|
||||
}
|
||||
Reference in New Issue
Block a user