Compare commits
9 Commits
burn/20260
...
mimo/code/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bc3e0879d | ||
| aab3e607eb | |||
| fe56ece1ad | |||
| bf477382ba | |||
| fba972f8be | |||
| 6786e65f3d | |||
| 62a6581827 | |||
| 797f32a7fe | |||
| 80eb4ff7ea |
6
app.js
6
app.js
@@ -1,4 +1,4 @@
|
||||
import * as THREE from 'three';
|
||||
import ResonanceVisualizer from './nexus/components/resonance-visualizer.js';\nimport * as THREE from 'three';
|
||||
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
|
||||
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
||||
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
|
||||
@@ -597,7 +597,7 @@ class PSELayer {
|
||||
|
||||
let pseLayer;
|
||||
|
||||
let metaLayer, neuroBridge, cbr, symbolicPlanner, knowledgeGraph, blackboard, symbolicEngine, calibrator;
|
||||
let resonanceViz, metaLayer, neuroBridge, cbr, symbolicPlanner, knowledgeGraph, blackboard, symbolicEngine, calibrator;
|
||||
let agentFSMs = {};
|
||||
|
||||
function setupGOFAI() {
|
||||
@@ -666,7 +666,7 @@ async function init() {
|
||||
scene = new THREE.Scene();
|
||||
scene.fog = new THREE.FogExp2(0x050510, 0.012);
|
||||
|
||||
setupGOFAI();
|
||||
setupGOFAI();\n resonanceViz = new ResonanceVisualizer(scene);
|
||||
camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||
camera.position.copy(playerPos);
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
|
||||
class MemoryOptimizer {
|
||||
constructor(options = {}) {
|
||||
this.threshold = options.threshold || 0.8;
|
||||
this.decayRate = options.decayRate || 0.05;
|
||||
this.threshold = options.threshold || 0.3;
|
||||
this.decayRate = options.decayRate || 0.01;
|
||||
this.lastRun = Date.now();
|
||||
}
|
||||
optimize(memory) {
|
||||
console.log('Optimizing memory...');
|
||||
// Heuristic-based pruning
|
||||
return memory.filter(m => m.strength > this.threshold);
|
||||
optimize(memories) {
|
||||
const now = Date.now();
|
||||
const elapsed = (now - this.lastRun) / 1000;
|
||||
this.lastRun = now;
|
||||
return memories.map(m => {
|
||||
const decay = (m.importance || 1) * this.decayRate * elapsed;
|
||||
return { ...m, strength: Math.max(0, (m.strength || 1) - decay) };
|
||||
}).filter(m => m.strength > this.threshold || m.locked);
|
||||
}
|
||||
}
|
||||
export default MemoryOptimizer;
|
||||
|
||||
@@ -694,15 +694,61 @@ const SpatialMemory = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── CONTEXT COMPACTION (issue #675) ──────────────────
|
||||
const COMPACT_CONTENT_MAXLEN = 80; // max chars for low-strength memories
|
||||
const COMPACT_STRENGTH_THRESHOLD = 0.5; // below this, content gets truncated
|
||||
const COMPACT_MAX_CONNECTIONS = 5; // cap connections per memory
|
||||
const COMPACT_POSITION_DECIMALS = 1; // round positions to 1 decimal
|
||||
|
||||
function _compactPosition(pos) {
|
||||
const factor = Math.pow(10, COMPACT_POSITION_DECIMALS);
|
||||
return pos.map(v => Math.round(v * factor) / factor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deterministically compact a memory for storage.
|
||||
* Same input always produces same output — no randomness.
|
||||
* Strong memories keep full fidelity; weak memories get truncated.
|
||||
*/
|
||||
function _compactMemory(o) {
|
||||
const strength = o.mesh.userData.strength || 0.7;
|
||||
const content = o.data.content || '';
|
||||
const connections = o.data.connections || [];
|
||||
|
||||
// Deterministic content truncation for weak memories
|
||||
let compactContent = content;
|
||||
if (strength < COMPACT_STRENGTH_THRESHOLD && content.length > COMPACT_CONTENT_MAXLEN) {
|
||||
compactContent = content.slice(0, COMPACT_CONTENT_MAXLEN) + '\u2026';
|
||||
}
|
||||
|
||||
// Cap connections (keep first N, deterministic)
|
||||
const compactConnections = connections.length > COMPACT_MAX_CONNECTIONS
|
||||
? connections.slice(0, COMPACT_MAX_CONNECTIONS)
|
||||
: connections;
|
||||
|
||||
return {
|
||||
id: o.data.id,
|
||||
content: compactContent,
|
||||
category: o.region,
|
||||
position: _compactPosition([o.mesh.position.x, o.mesh.position.y - 1.5, o.mesh.position.z]),
|
||||
source: o.data.source || 'unknown',
|
||||
timestamp: o.data.timestamp || o.mesh.userData.createdAt,
|
||||
strength: Math.round(strength * 100) / 100, // 2 decimal precision
|
||||
connections: compactConnections
|
||||
};
|
||||
}
|
||||
|
||||
// ─── PERSISTENCE ─────────────────────────────────────
|
||||
function exportIndex() {
|
||||
function exportIndex(options = {}) {
|
||||
const compact = options.compact !== false; // compact by default
|
||||
return {
|
||||
version: 1,
|
||||
exportedAt: new Date().toISOString(),
|
||||
compacted: compact,
|
||||
regions: Object.fromEntries(
|
||||
Object.entries(REGIONS).map(([k, v]) => [k, { label: v.label, center: v.center, radius: v.radius, color: v.color }])
|
||||
),
|
||||
memories: Object.values(_memoryObjects).map(o => ({
|
||||
memories: Object.values(_memoryObjects).map(o => compact ? _compactMemory(o) : {
|
||||
id: o.data.id,
|
||||
content: o.data.content,
|
||||
category: o.region,
|
||||
@@ -711,7 +757,7 @@ const SpatialMemory = (() => {
|
||||
timestamp: o.data.timestamp || o.mesh.userData.createdAt,
|
||||
strength: o.mesh.userData.strength || 0.7,
|
||||
connections: o.data.connections || []
|
||||
}))
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1340,74 +1340,6 @@ class MnemosyneArchive:
|
||||
results.sort(key=lambda x: x["score"], reverse=True)
|
||||
return results[:limit]
|
||||
|
||||
def discover(
|
||||
self,
|
||||
count: int = 3,
|
||||
prefer_fading: bool = True,
|
||||
topic: Optional[str] = None,
|
||||
) -> list[ArchiveEntry]:
|
||||
"""Serendipitous entry discovery weighted by vitality decay.
|
||||
|
||||
Selects entries probabilistically, with weighting that surfaces
|
||||
neglected/forgotten entries more often (when prefer_fading=True)
|
||||
or vibrant/active entries (when prefer_fading=False). Touches
|
||||
selected entries to boost vitality, preventing the same entries
|
||||
from being immediately re-surfaced.
|
||||
|
||||
Args:
|
||||
count: Number of entries to discover (default 3).
|
||||
prefer_fading: If True (default), weight toward fading entries.
|
||||
If False, weight toward vibrant entries.
|
||||
topic: If set, restrict to entries with this topic (case-insensitive).
|
||||
|
||||
Returns:
|
||||
List of ArchiveEntry, up to count entries.
|
||||
"""
|
||||
import random
|
||||
|
||||
candidates = list(self._entries.values())
|
||||
|
||||
if not candidates:
|
||||
return []
|
||||
|
||||
if topic:
|
||||
topic_lower = topic.lower()
|
||||
candidates = [e for e in candidates if topic_lower in [t.lower() for t in e.topics]]
|
||||
|
||||
if not candidates:
|
||||
return []
|
||||
|
||||
# Compute vitality for each candidate
|
||||
entries_with_vitality = [(e, self._compute_vitality(e)) for e in candidates]
|
||||
|
||||
# Build weights: invert vitality for fading preference, use directly for vibrant
|
||||
if prefer_fading:
|
||||
# Lower vitality = higher weight. Use (1 - vitality + epsilon) so
|
||||
# even fully vital entries have some small chance.
|
||||
weights = [1.0 - v + 0.01 for _, v in entries_with_vitality]
|
||||
else:
|
||||
# Higher vitality = higher weight. Use (vitality + epsilon).
|
||||
weights = [v + 0.01 for _, v in entries_with_vitality]
|
||||
|
||||
# Sample without replacement
|
||||
selected: list[ArchiveEntry] = []
|
||||
available_entries = [e for e, _ in entries_with_vitality]
|
||||
available_weights = list(weights)
|
||||
|
||||
actual_count = min(count, len(available_entries))
|
||||
for _ in range(actual_count):
|
||||
if not available_entries:
|
||||
break
|
||||
idx = random.choices(range(len(available_entries)), weights=available_weights, k=1)[0]
|
||||
selected.append(available_entries.pop(idx))
|
||||
available_weights.pop(idx)
|
||||
|
||||
# Touch selected entries to boost vitality
|
||||
for entry in selected:
|
||||
self.touch(entry.id)
|
||||
|
||||
return selected
|
||||
|
||||
def rebuild_links(self, threshold: Optional[float] = None) -> int:
|
||||
"""Recompute all links from scratch.
|
||||
|
||||
|
||||
@@ -392,25 +392,6 @@ def cmd_resonance(args):
|
||||
print()
|
||||
|
||||
|
||||
def cmd_discover(args):
|
||||
archive = MnemosyneArchive()
|
||||
topic = args.topic if args.topic else None
|
||||
results = archive.discover(
|
||||
count=args.count,
|
||||
prefer_fading=not args.vibrant,
|
||||
topic=topic,
|
||||
)
|
||||
if not results:
|
||||
print("No entries to discover.")
|
||||
return
|
||||
for entry in results:
|
||||
v = archive.get_vitality(entry.id)
|
||||
print(f"[{entry.id[:8]}] {entry.title}")
|
||||
print(f" Topics: {', '.join(entry.topics) if entry.topics else '(none)'}")
|
||||
print(f" Vitality: {v['vitality']:.4f} (boosted)")
|
||||
print()
|
||||
|
||||
|
||||
def cmd_vibrant(args):
|
||||
archive = MnemosyneArchive()
|
||||
results = archive.vibrant(limit=args.limit)
|
||||
@@ -518,11 +499,6 @@ def main():
|
||||
rs.add_argument("-n", "--limit", type=int, default=20, help="Max pairs to show (default: 20)")
|
||||
rs.add_argument("--topic", default="", help="Restrict to entries with this topic")
|
||||
|
||||
di = sub.add_parser("discover", help="Serendipitous entry exploration")
|
||||
di.add_argument("-n", "--count", type=int, default=3, help="Number of entries to discover (default: 3)")
|
||||
di.add_argument("-t", "--topic", default="", help="Filter to entries with this topic")
|
||||
di.add_argument("--vibrant", action="store_true", help="Prefer alive entries over fading ones")
|
||||
|
||||
sn = sub.add_parser("snapshot", help="Point-in-time backup and restore")
|
||||
sn_sub = sn.add_subparsers(dest="snapshot_cmd")
|
||||
sn_create = sn_sub.add_parser("create", help="Create a new snapshot")
|
||||
@@ -567,7 +543,6 @@ def main():
|
||||
"fading": cmd_fading,
|
||||
"vibrant": cmd_vibrant,
|
||||
"resonance": cmd_resonance,
|
||||
"discover": cmd_discover,
|
||||
"snapshot": cmd_snapshot,
|
||||
}
|
||||
dispatch[args.command](args)
|
||||
|
||||
14
nexus/mnemosyne/reasoner.py
Normal file
14
nexus/mnemosyne/reasoner.py
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
class Reasoner:
|
||||
def __init__(self, rules):
|
||||
self.rules = rules
|
||||
def evaluate(self, entries):
|
||||
return [r['action'] for r in self.rules if self._check(r['condition'], entries)]
|
||||
def _check(self, cond, entries):
|
||||
if cond.startswith('count'):
|
||||
# e.g. count(type=anomaly)>3
|
||||
p = cond.replace('count(', '').split(')')
|
||||
key, val = p[0].split('=')
|
||||
count = sum(1 for e in entries if e.get(key) == val)
|
||||
return eval(f"{count}{p[1]}")
|
||||
return False
|
||||
22
nexus/mnemosyne/resonance_linker.py
Normal file
22
nexus/mnemosyne/resonance_linker.py
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
"""Resonance Linker — Finds second-degree connections in the holographic graph."""
|
||||
|
||||
class ResonanceLinker:
|
||||
def __init__(self, archive):
|
||||
self.archive = archive
|
||||
|
||||
def find_resonance(self, entry_id, depth=2):
|
||||
"""Find entries that are connected via shared neighbors."""
|
||||
if entry_id not in self.archive._entries: return []
|
||||
|
||||
entry = self.archive._entries[entry_id]
|
||||
neighbors = set(entry.links)
|
||||
resonance = {}
|
||||
|
||||
for neighbor_id in neighbors:
|
||||
if neighbor_id in self.archive._entries:
|
||||
for second_neighbor in self.archive._entries[neighbor_id].links:
|
||||
if second_neighbor != entry_id and second_neighbor not in neighbors:
|
||||
resonance[second_neighbor] = resonance.get(second_neighbor, 0) + 1
|
||||
|
||||
return sorted(resonance.items(), key=lambda x: x[1], reverse=True)
|
||||
6
nexus/mnemosyne/rules.json
Normal file
6
nexus/mnemosyne/rules.json
Normal file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"condition": "count(type=anomaly)>3",
|
||||
"action": "alert"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user