Implement Sophia MVP stages 4-7 (introspection, queries, persistence, polish)
Brings the system from "engrams cluster + form synapses" to a complete end-to-end demo: ingest text, watch it cluster, ask questions, restart with state intact. - Stage 4: birth introspection (taxonomy/goals/open_questions via LLM, bounded by the global parallel-op semaphore), per-engram memory log, click-to-inspect side panel. - Stage 5: queries as conversations. POST /api/galaxy/:id/query embeds the question, materializes a pinned Query-Engram at the donut center, runs broadcast retrieval (global cosine scan + 1-hop synaptic expansion with attenuation) and fans out responder LLM calls. The integrator runs every 2s on accumulated snippets and streams the refining answer back over SSE; responders briefly transition to Conversing on the WS bus so the right dots light up. - Stage 6: snapshot persistence. sled-backed store keyed by galaxy id, JSON-encoded values (bincode chokes on internally-tagged enums like Manifest/MemoryKind), 60s periodic snapshot task, hydrate-on-boot, DELETE /api/galaxy/:id wired through. State survives kill -9. - Stage 7: HUD additions (sim ticks/sec, LLM queue depth, FPS) via a new GET /api/stats polled at 1Hz. `sophia demo` subcommand boots the server then auto-ingests a 50-paragraph corpus baked into the binary with include_str!. README quickstart added. Token caps for query_responder/integrator bumped (gemma-4-e4b is a thinking model — output budget must cover hidden reasoning + visible answer, otherwise content comes back empty). Pinned engrams skip physics; their tick scheduling is also skipped at materialization so they stay perfectly still at the donut center. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,18 @@ export class EngramMesh {
|
||||
/** Optional hook fired when an engram's base color is set/updated. The
|
||||
* trail renderer subscribes so head + tail share the same colour. */
|
||||
public onColorAssigned: ((idx: number, r: number, g: number, b: number) => void) | null = null;
|
||||
/** Reverse lookup: instance_idx → engram UUID. Filled on upsert; used by
|
||||
* the click-picker to map a raycast hit back to an engram id. */
|
||||
public readonly idxToId: string[] = [];
|
||||
/** Forward lookup: engram UUID → instance_idx. Mirrors `idxToId` so live
|
||||
* state-change events (which carry the UUID, not the slot index) can find
|
||||
* the right vertex to re-paint. */
|
||||
private readonly idToIdx = new Map<string, number>();
|
||||
|
||||
/** Expose the underlying `THREE.Points` so the scene can raycast against it. */
|
||||
pointsObject(): THREE.Points {
|
||||
return this.points;
|
||||
}
|
||||
|
||||
constructor(scene: THREE.Scene, capacity = 5000) {
|
||||
this.capacity = capacity;
|
||||
@@ -125,6 +137,12 @@ export class EngramMesh {
|
||||
geom.setAttribute("aSize", this.sizeAttr);
|
||||
geom.setAttribute("aHash", this.hashAttr);
|
||||
geom.setDrawRange(0, 0);
|
||||
// Set a permanent oversized bounding sphere. Without this, three.js
|
||||
// computes one once based on the initial all-zero positions (radius 0)
|
||||
// and Points.raycast() short-circuits — every click misses. Recomputing
|
||||
// per frame is expensive; a giant fixed sphere always passes the early
|
||||
// reject and the per-vertex test then runs normally.
|
||||
geom.boundingSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 100_000);
|
||||
|
||||
this.material = new THREE.ShaderMaterial({
|
||||
vertexShader: VERT_SHADER,
|
||||
@@ -197,6 +215,28 @@ export class EngramMesh {
|
||||
|
||||
if (idx + 1 > this.maxIdx) this.maxIdx = idx + 1;
|
||||
this.points.geometry.setDrawRange(0, this.maxIdx);
|
||||
this.idxToId[idx] = e.id;
|
||||
this.idToIdx.set(e.id, idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-paint the dot for an existing engram when its lifecycle state
|
||||
* changes (Stage 5: responder lights up to Conversing while answering a
|
||||
* query, Query-Engram transitions Searching→Memorize at completion).
|
||||
* Silently ignored if the engram isn't known yet — state-change events
|
||||
* for unfamiliar ids can race ahead of the corresponding `engram_created`
|
||||
* over the WS bus during a reconnect window.
|
||||
*/
|
||||
setStateById(id: string, state: string): void {
|
||||
const idx = this.idToIdx.get(id);
|
||||
if (idx === undefined) return;
|
||||
const color = STATE_COLOR[state] ?? STATE_COLOR.idle;
|
||||
const colorArr = this.colorAttr.array as Float32Array;
|
||||
colorArr[idx * 3] = color[0];
|
||||
colorArr[idx * 3 + 1] = color[1];
|
||||
colorArr[idx * 3 + 2] = color[2];
|
||||
this.colorAttr.needsUpdate = true;
|
||||
this.onColorAssigned?.(idx, color[0], color[1], color[2]);
|
||||
}
|
||||
|
||||
applyPositionFrame(frame: PositionFrame): void {
|
||||
|
||||
Reference in New Issue
Block a user