Compare commits
9 Commits
mimo/build
...
mimo/resea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
869a7711e3 | ||
| aab3e607eb | |||
| fe56ece1ad | |||
| bf477382ba | |||
| fba972f8be | |||
| 6786e65f3d | |||
| 62a6581827 | |||
| 797f32a7fe | |||
| 80eb4ff7ea |
51
.gitea.yml
51
.gitea.yml
@@ -15,54 +15,3 @@ protection:
|
|||||||
- perplexity
|
- perplexity
|
||||||
required_reviewers:
|
required_reviewers:
|
||||||
- Timmy # Owner gate for hermes-agent
|
- Timmy # Owner gate for hermes-agent
|
||||||
main:
|
|
||||||
require_pull_request: true
|
|
||||||
required_approvals: 1
|
|
||||||
dismiss_stale_approvals: true
|
|
||||||
require_ci_to_pass: true
|
|
||||||
block_force_push: true
|
|
||||||
block_deletion: true
|
|
||||||
>>>>>>> replace
|
|
||||||
</source>
|
|
||||||
|
|
||||||
CODEOWNERS
|
|
||||||
<source>
|
|
||||||
<<<<<<< search
|
|
||||||
protection:
|
|
||||||
main:
|
|
||||||
required_status_checks:
|
|
||||||
- "ci/unit-tests"
|
|
||||||
- "ci/integration"
|
|
||||||
required_pull_request_reviews:
|
|
||||||
- "1 approval"
|
|
||||||
restrictions:
|
|
||||||
- "block force push"
|
|
||||||
- "block deletion"
|
|
||||||
enforce_admins: true
|
|
||||||
|
|
||||||
the-nexus:
|
|
||||||
required_status_checks: []
|
|
||||||
required_pull_request_reviews:
|
|
||||||
- "1 approval"
|
|
||||||
restrictions:
|
|
||||||
- "block force push"
|
|
||||||
- "block deletion"
|
|
||||||
enforce_admins: true
|
|
||||||
|
|
||||||
timmy-home:
|
|
||||||
required_status_checks: []
|
|
||||||
required_pull_request_reviews:
|
|
||||||
- "1 approval"
|
|
||||||
restrictions:
|
|
||||||
- "block force push"
|
|
||||||
- "block deletion"
|
|
||||||
enforce_admins: true
|
|
||||||
|
|
||||||
timmy-config:
|
|
||||||
required_status_checks: []
|
|
||||||
required_pull_request_reviews:
|
|
||||||
- "1 approval"
|
|
||||||
restrictions:
|
|
||||||
- "block force push"
|
|
||||||
- "block deletion"
|
|
||||||
enforce_admins: true
|
|
||||||
|
|||||||
42
app.js
42
app.js
@@ -1,9 +1,10 @@
|
|||||||
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 { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
|
||||||
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
||||||
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
|
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
|
||||||
import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
|
import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
|
||||||
import { SpatialMemory } from './nexus/components/spatial-memory.js';
|
import { SpatialMemory } from './nexus/components/spatial-memory.js';
|
||||||
|
import { SpatialAudio } from './nexus/components/spatial-audio.js';
|
||||||
import { MemoryBirth } from './nexus/components/memory-birth.js';
|
import { MemoryBirth } from './nexus/components/memory-birth.js';
|
||||||
import { MemoryOptimizer } from './nexus/components/memory-optimizer.js';
|
import { MemoryOptimizer } from './nexus/components/memory-optimizer.js';
|
||||||
import { MemoryInspect } from './nexus/components/memory-inspect.js';
|
import { MemoryInspect } from './nexus/components/memory-inspect.js';
|
||||||
@@ -597,7 +598,7 @@ class PSELayer {
|
|||||||
|
|
||||||
let pseLayer;
|
let pseLayer;
|
||||||
|
|
||||||
let metaLayer, neuroBridge, cbr, symbolicPlanner, knowledgeGraph, blackboard, symbolicEngine, calibrator;
|
let resonanceViz, metaLayer, neuroBridge, cbr, symbolicPlanner, knowledgeGraph, blackboard, symbolicEngine, calibrator;
|
||||||
let agentFSMs = {};
|
let agentFSMs = {};
|
||||||
|
|
||||||
function setupGOFAI() {
|
function setupGOFAI() {
|
||||||
@@ -666,7 +667,7 @@ async function init() {
|
|||||||
scene = new THREE.Scene();
|
scene = new THREE.Scene();
|
||||||
scene.fog = new THREE.FogExp2(0x050510, 0.012);
|
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 = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||||
camera.position.copy(playerPos);
|
camera.position.copy(playerPos);
|
||||||
|
|
||||||
@@ -704,17 +705,19 @@ async function init() {
|
|||||||
createParticles();
|
createParticles();
|
||||||
createDustParticles();
|
createDustParticles();
|
||||||
updateLoad(85);
|
updateLoad(85);
|
||||||
if (performanceTier !== "low") createAmbientStructures();
|
createAmbientStructures();
|
||||||
createAgentPresences();
|
createAgentPresences();
|
||||||
if (performanceTier !== "low") createThoughtStream();
|
createThoughtStream();
|
||||||
createHarnessPulse();
|
createHarnessPulse();
|
||||||
createSessionPowerMeter();
|
createSessionPowerMeter();
|
||||||
createWorkshopTerminal();
|
createWorkshopTerminal();
|
||||||
if (performanceTier !== "low") createAshStorm();
|
createAshStorm();
|
||||||
SpatialMemory.init(scene);
|
SpatialMemory.init(scene);
|
||||||
MemoryBirth.init(scene);
|
MemoryBirth.init(scene);
|
||||||
MemoryBirth.wrapSpatialMemory(SpatialMemory);
|
MemoryBirth.wrapSpatialMemory(SpatialMemory);
|
||||||
SpatialMemory.setCamera(camera);
|
SpatialMemory.setCamera(camera);
|
||||||
|
SpatialAudio.init(camera, scene);
|
||||||
|
SpatialAudio.bindSpatialMemory(SpatialMemory);
|
||||||
MemoryInspect.init({ onNavigate: _navigateToMemory });
|
MemoryInspect.init({ onNavigate: _navigateToMemory });
|
||||||
MemoryPulse.init(SpatialMemory);
|
MemoryPulse.init(SpatialMemory);
|
||||||
updateLoad(90);
|
updateLoad(90);
|
||||||
@@ -730,20 +733,14 @@ async function init() {
|
|||||||
fetchGiteaData();
|
fetchGiteaData();
|
||||||
setInterval(fetchGiteaData, 30000); // Refresh every 30s
|
setInterval(fetchGiteaData, 30000); // Refresh every 30s
|
||||||
|
|
||||||
// Quality-tier feature gating: only enable heavy post-processing on medium/high
|
composer = new EffectComposer(renderer);
|
||||||
if (performanceTier !== 'low') {
|
composer.addPass(new RenderPass(scene, camera));
|
||||||
composer = new EffectComposer(renderer);
|
const bloom = new UnrealBloomPass(
|
||||||
composer.addPass(new RenderPass(scene, camera));
|
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
||||||
const bloomStrength = performanceTier === 'high' ? 0.6 : 0.35;
|
0.6, 0.4, 0.85
|
||||||
const bloom = new UnrealBloomPass(
|
);
|
||||||
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
composer.addPass(bloom);
|
||||||
bloomStrength, 0.4, 0.85
|
composer.addPass(new SMAAPass(window.innerWidth, window.innerHeight));
|
||||||
);
|
|
||||||
composer.addPass(bloom);
|
|
||||||
composer.addPass(new SMAAPass(window.innerWidth, window.innerHeight));
|
|
||||||
} else {
|
|
||||||
composer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateLoad(95);
|
updateLoad(95);
|
||||||
|
|
||||||
@@ -2932,6 +2929,7 @@ function gameLoop() {
|
|||||||
// Project Mnemosyne - Memory Orb Animation
|
// Project Mnemosyne - Memory Orb Animation
|
||||||
if (typeof animateMemoryOrbs === 'function') {
|
if (typeof animateMemoryOrbs === 'function') {
|
||||||
SpatialMemory.update(delta);
|
SpatialMemory.update(delta);
|
||||||
|
SpatialAudio.update(delta);
|
||||||
MemoryBirth.update(delta);
|
MemoryBirth.update(delta);
|
||||||
MemoryPulse.update();
|
MemoryPulse.update();
|
||||||
animateMemoryOrbs(delta);
|
animateMemoryOrbs(delta);
|
||||||
@@ -3133,7 +3131,7 @@ function gameLoop() {
|
|||||||
core.material.emissiveIntensity = 1.5 + Math.sin(elapsed * 2) * 0.5;
|
core.material.emissiveIntensity = 1.5 + Math.sin(elapsed * 2) * 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (composer) { composer.render(); } else { renderer.render(scene, camera); }
|
composer.render();
|
||||||
|
|
||||||
updateAshStorm(delta, elapsed);
|
updateAshStorm(delta, elapsed);
|
||||||
|
|
||||||
@@ -3172,7 +3170,7 @@ function onResize() {
|
|||||||
camera.aspect = w / h;
|
camera.aspect = w / h;
|
||||||
camera.updateProjectionMatrix();
|
camera.updateProjectionMatrix();
|
||||||
renderer.setSize(w, h);
|
renderer.setSize(w, h);
|
||||||
if (composer) composer.setSize(w, h);
|
composer.setSize(w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══ AGENT SIMULATION ═══
|
// ═══ AGENT SIMULATION ═══
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
|
|
||||||
class MemoryOptimizer {
|
class MemoryOptimizer {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
this.threshold = options.threshold || 0.8;
|
this.threshold = options.threshold || 0.3;
|
||||||
this.decayRate = options.decayRate || 0.05;
|
this.decayRate = options.decayRate || 0.01;
|
||||||
|
this.lastRun = Date.now();
|
||||||
}
|
}
|
||||||
optimize(memory) {
|
optimize(memories) {
|
||||||
console.log('Optimizing memory...');
|
const now = Date.now();
|
||||||
// Heuristic-based pruning
|
const elapsed = (now - this.lastRun) / 1000;
|
||||||
return memory.filter(m => m.strength > this.threshold);
|
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;
|
export default MemoryOptimizer;
|
||||||
|
|||||||
242
nexus/components/spatial-audio.js
Normal file
242
nexus/components/spatial-audio.js
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
|
// SPATIAL AUDIO MANAGER — Nexus Spatial Sound for Mnemosyne
|
||||||
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
|
//
|
||||||
|
// Attaches a Three.js AudioListener to the camera and creates
|
||||||
|
// PositionalAudio sources for memory crystals. Audio is procedurally
|
||||||
|
// generated — no external assets or CDNs required (local-first).
|
||||||
|
//
|
||||||
|
// Each region gets a distinct tone. Proximity controls volume and
|
||||||
|
// panning. Designed to layer on top of SpatialMemory without
|
||||||
|
// modifying it.
|
||||||
|
//
|
||||||
|
// Usage from app.js:
|
||||||
|
// SpatialAudio.init(camera, scene);
|
||||||
|
// SpatialAudio.bindSpatialMemory(SpatialMemory);
|
||||||
|
// SpatialAudio.update(delta); // call in animation loop
|
||||||
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
const SpatialAudio = (() => {
|
||||||
|
|
||||||
|
// ─── CONFIG ──────────────────────────────────────────────
|
||||||
|
const REGION_TONES = {
|
||||||
|
engineering: { freq: 220, type: 'sine' }, // A3
|
||||||
|
social: { freq: 261, type: 'triangle' }, // C4
|
||||||
|
knowledge: { freq: 329, type: 'sine' }, // E4
|
||||||
|
projects: { freq: 392, type: 'triangle' }, // G4
|
||||||
|
working: { freq: 440, type: 'sine' }, // A4
|
||||||
|
archive: { freq: 110, type: 'sine' }, // A2
|
||||||
|
user_pref: { freq: 349, type: 'triangle' }, // F4
|
||||||
|
project: { freq: 392, type: 'sine' }, // G4
|
||||||
|
tool: { freq: 493, type: 'triangle' }, // B4
|
||||||
|
general: { freq: 293, type: 'sine' }, // D4
|
||||||
|
};
|
||||||
|
const MAX_AUDIBLE_DIST = 40; // distance at which volume reaches 0
|
||||||
|
const REF_DIST = 5; // full volume within this range
|
||||||
|
const ROLLOFF = 1.5;
|
||||||
|
const BASE_VOLUME = 0.12; // master volume cap per source
|
||||||
|
const AMBIENT_VOLUME = 0.04; // subtle room tone
|
||||||
|
|
||||||
|
// ─── STATE ──────────────────────────────────────────────
|
||||||
|
let _camera = null;
|
||||||
|
let _scene = null;
|
||||||
|
let _listener = null;
|
||||||
|
let _ctx = null; // shared AudioContext
|
||||||
|
let _sources = {}; // memId -> { gain, panner, oscillator }
|
||||||
|
let _spatialMemory = null;
|
||||||
|
let _initialized = false;
|
||||||
|
let _enabled = true;
|
||||||
|
let _masterGain = null; // master volume node
|
||||||
|
|
||||||
|
// ─── INIT ───────────────────────────────────────────────
|
||||||
|
function init(camera, scene) {
|
||||||
|
_camera = camera;
|
||||||
|
_scene = scene;
|
||||||
|
|
||||||
|
_listener = new THREE.AudioListener();
|
||||||
|
camera.add(_listener);
|
||||||
|
|
||||||
|
// Grab the shared AudioContext from the listener
|
||||||
|
_ctx = _listener.context;
|
||||||
|
_masterGain = _ctx.createGain();
|
||||||
|
_masterGain.gain.value = 1.0;
|
||||||
|
_masterGain.connect(_ctx.destination);
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
console.info('[SpatialAudio] Initialized — AudioContext state:', _ctx.state);
|
||||||
|
|
||||||
|
// Browsers require a user gesture to resume audio context
|
||||||
|
if (_ctx.state === 'suspended') {
|
||||||
|
const resume = () => {
|
||||||
|
_ctx.resume().then(() => {
|
||||||
|
console.info('[SpatialAudio] AudioContext resumed');
|
||||||
|
document.removeEventListener('click', resume);
|
||||||
|
document.removeEventListener('keydown', resume);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
document.addEventListener('click', resume);
|
||||||
|
document.addEventListener('keydown', resume);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── BIND TO SPATIAL MEMORY ─────────────────────────────
|
||||||
|
function bindSpatialMemory(sm) {
|
||||||
|
_spatialMemory = sm;
|
||||||
|
// Create sources for any existing memories
|
||||||
|
const all = sm.getAllMemories();
|
||||||
|
all.forEach(mem => _ensureSource(mem));
|
||||||
|
console.info('[SpatialAudio] Bound to SpatialMemory —', Object.keys(_sources).length, 'audio sources');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── CREATE A PROCEDURAL TONE SOURCE ────────────────────
|
||||||
|
function _ensureSource(mem) {
|
||||||
|
if (!_ctx || !_enabled || _sources[mem.id]) return;
|
||||||
|
|
||||||
|
const regionKey = mem.category || 'working';
|
||||||
|
const tone = REGION_TONES[regionKey] || REGION_TONES.working;
|
||||||
|
|
||||||
|
// Procedural oscillator
|
||||||
|
const osc = _ctx.createOscillator();
|
||||||
|
osc.type = tone.type;
|
||||||
|
osc.frequency.value = tone.freq + _hashOffset(mem.id); // slight per-crystal detune
|
||||||
|
|
||||||
|
const gain = _ctx.createGain();
|
||||||
|
gain.gain.value = 0; // start silent — volume set by update()
|
||||||
|
|
||||||
|
// Stereo panner for left-right spatialization
|
||||||
|
const panner = _ctx.createStereoPanner();
|
||||||
|
panner.pan.value = 0;
|
||||||
|
|
||||||
|
osc.connect(gain);
|
||||||
|
gain.connect(panner);
|
||||||
|
panner.connect(_masterGain);
|
||||||
|
|
||||||
|
osc.start();
|
||||||
|
|
||||||
|
_sources[mem.id] = { osc, gain, panner, region: regionKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small deterministic pitch offset so crystals in the same region don't phase-lock
|
||||||
|
function _hashOffset(id) {
|
||||||
|
let h = 0;
|
||||||
|
for (let i = 0; i < id.length; i++) {
|
||||||
|
h = ((h << 5) - h) + id.charCodeAt(i);
|
||||||
|
h |= 0;
|
||||||
|
}
|
||||||
|
return (Math.abs(h) % 40) - 20; // ±20 Hz
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── PER-FRAME UPDATE ───────────────────────────────────
|
||||||
|
function update() {
|
||||||
|
if (!_initialized || !_enabled || !_spatialMemory || !_camera) return;
|
||||||
|
|
||||||
|
const camPos = _camera.position;
|
||||||
|
const memories = _spatialMemory.getAllMemories();
|
||||||
|
|
||||||
|
// Ensure sources for newly placed memories
|
||||||
|
memories.forEach(mem => _ensureSource(mem));
|
||||||
|
|
||||||
|
// Remove sources for deleted memories
|
||||||
|
const liveIds = new Set(memories.map(m => m.id));
|
||||||
|
Object.keys(_sources).forEach(id => {
|
||||||
|
if (!liveIds.has(id)) {
|
||||||
|
_removeSource(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update each source's volume & panning based on camera distance
|
||||||
|
memories.forEach(mem => {
|
||||||
|
const src = _sources[mem.id];
|
||||||
|
if (!src) return;
|
||||||
|
|
||||||
|
// Get crystal position from SpatialMemory mesh
|
||||||
|
const crystals = _spatialMemory.getCrystalMeshes();
|
||||||
|
let meshPos = null;
|
||||||
|
for (const mesh of crystals) {
|
||||||
|
if (mesh.userData.memId === mem.id) {
|
||||||
|
meshPos = mesh.position;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!meshPos) return;
|
||||||
|
|
||||||
|
const dx = meshPos.x - camPos.x;
|
||||||
|
const dy = meshPos.y - camPos.y;
|
||||||
|
const dz = meshPos.z - camPos.z;
|
||||||
|
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
|
|
||||||
|
// Volume rolloff (inverse distance model)
|
||||||
|
let vol = 0;
|
||||||
|
if (dist < MAX_AUDIBLE_DIST) {
|
||||||
|
vol = BASE_VOLUME / (1 + ROLLOFF * (dist - REF_DIST));
|
||||||
|
vol = Math.max(0, Math.min(BASE_VOLUME, vol));
|
||||||
|
}
|
||||||
|
src.gain.gain.setTargetAtTime(vol, _ctx.currentTime, 0.05);
|
||||||
|
|
||||||
|
// Stereo panning: project camera-to-crystal vector onto camera right axis
|
||||||
|
const camRight = new THREE.Vector3();
|
||||||
|
_camera.getWorldDirection(camRight);
|
||||||
|
camRight.cross(_camera.up).normalize();
|
||||||
|
const toCrystal = new THREE.Vector3(dx, 0, dz).normalize();
|
||||||
|
const pan = THREE.MathUtils.clamp(toCrystal.dot(camRight), -1, 1);
|
||||||
|
src.panner.pan.setTargetAtTime(pan, _ctx.currentTime, 0.05);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _removeSource(id) {
|
||||||
|
const src = _sources[id];
|
||||||
|
if (!src) return;
|
||||||
|
try {
|
||||||
|
src.osc.stop();
|
||||||
|
src.osc.disconnect();
|
||||||
|
src.gain.disconnect();
|
||||||
|
src.panner.disconnect();
|
||||||
|
} catch (_) { /* already stopped */ }
|
||||||
|
delete _sources[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── CONTROLS ───────────────────────────────────────────
|
||||||
|
function setEnabled(enabled) {
|
||||||
|
_enabled = enabled;
|
||||||
|
if (!_enabled) {
|
||||||
|
// Silence all sources
|
||||||
|
Object.values(_sources).forEach(src => {
|
||||||
|
src.gain.gain.setTargetAtTime(0, _ctx.currentTime, 0.05);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.info('[SpatialAudio]', enabled ? 'Enabled' : 'Disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEnabled() {
|
||||||
|
return _enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMasterVolume(vol) {
|
||||||
|
if (_masterGain) {
|
||||||
|
_masterGain.gain.setTargetAtTime(
|
||||||
|
THREE.MathUtils.clamp(vol, 0, 1),
|
||||||
|
_ctx.currentTime,
|
||||||
|
0.05
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveSourceCount() {
|
||||||
|
return Object.keys(_sources).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── API ────────────────────────────────────────────────
|
||||||
|
return {
|
||||||
|
init,
|
||||||
|
bindSpatialMemory,
|
||||||
|
update,
|
||||||
|
setEnabled,
|
||||||
|
isEnabled,
|
||||||
|
setMasterVolume,
|
||||||
|
getActiveSourceCount,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
export { SpatialAudio };
|
||||||
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