Compare commits

..

7 Commits

Author SHA1 Message Date
93f3722a36 feat(mnemosyne): add styles for clear button 2026-04-10 23:40:34 +00:00
3e3e451820 feat(mnemosyne): implement clearMemoryFeed logic 2026-04-10 23:40:32 +00:00
14b75792fe feat(mnemosyne): add clear button to memory feed UI 2026-04-10 23:40:29 +00:00
66c0433fc3 feat: add memory activity feed CSS styles
Some checks failed
CI / test (pull_request) Failing after 10s
CI / validate (pull_request) Failing after 13s
Review Approval Gate / verify-review (pull_request) Failing after 3s
Part of #1164
2026-04-10 20:43:08 +00:00
332789199f feat: add memory activity feed panel to index.html
Part of #1164
2026-04-10 20:42:47 +00:00
2e0853510d feat: add WS memory bridge — handleMemoryMessage, sync, activity feed
- handleMemoryMessage(): handles place/remove/update/sync_response actions
- Sync handshake: sends sync_request on WS open
- Memory activity feed: shows last 5 memory events with auto-dismiss
- Part of #1164
2026-04-10 20:42:25 +00:00
31fdd01931 feat: add importMemories() and updateMemory() for live WS bridge
- importMemories(): batch import array of memories, dedup by id
- updateMemory(): update strength/content/connections without moving crystal
- _rebuildConnections(): helper to refresh connection lines on update

Part of #1164
2026-04-10 20:41:33 +00:00
7 changed files with 323 additions and 88 deletions

3
.gitignore vendored
View File

@@ -4,6 +4,3 @@ nexus/__pycache__/
tests/__pycache__/
mempalace/__pycache__/
.aider*
# Prevent agents from writing to wrong path (see issue #1145)
public/nexus/

View File

@@ -42,17 +42,6 @@ Current repo contents are centered on:
Do not tell contributors to run Vite or edit a nonexistent root frontend on current `main`.
If browser/UI work is being restored, it must happen through the migration backlog and land back here.
## Canonical File Paths
**Frontend code lives at repo ROOT, NOT in `public/nexus/`:**
- `app.js` — main Three.js app (GOFAI, 3D world, all frontend logic)
- `index.html` — main HTML shell
- `style.css` — styles
- `server.py` — websocket bridge
- `gofai_worker.js` — web worker for off-thread reasoning
**DO NOT write to `public/nexus/`** — this path is gitignored. Agents historically wrote here by mistake, creating corrupt duplicates. See issue #1145 and `INVESTIGATION_ISSUE_1145.md`.
## Hard Rules
1. One canonical 3D repo only: `Timmy_Foundation/the-nexus`
@@ -61,7 +50,6 @@ If browser/UI work is being restored, it must happen through the migration backl
4. Telemetry and durable truth flow through Hermes harness
5. OpenClaw remains a sidecar, not the governing authority
6. Before claiming visual validation, prove the app being viewed actually comes from current `the-nexus`
7. **NEVER write frontend files to `public/nexus/`** — use repo root paths listed above
## Validation Rule

View File

@@ -1,72 +0,0 @@
# Investigation Report: Missing Source Code — Classical AI Commits Disappearing
**Issue:** #1145
**Date:** 2026-04-10
**Investigator:** mimo-v2-pro swarm worker
## Summary
**The classical AI code is NOT missing. It is fully present in root `app.js` (3302 lines).**
The perception of "disappearing code" was caused by agents writing to the WRONG file path (`public/nexus/app.js` instead of root `app.js`), creating corrupt duplicate files that were repeatedly overwritten and eventually deleted.
## Root Cause
**Explanation #1 confirmed: Duplicate agents on different machines overwriting each other's commits.**
Multiple Google AI Agent instances wrote GOFAI implementations to `public/nexus/app.js` — a path that does not correspond to the canonical app structure. These commits kept overwriting each other:
| Commit | Date | What happened |
|--------|------|---------------|
| `8943cf5` | 2026-03-30 | Symbolic reasoning engine written to `public/nexus/app.js` (+2280 lines) |
| `e2df240` | 2026-03-30 | Phase 3 Neuro-Symbolic Bridge — overwrote to 284 lines of HTML (wrong path) |
| `7f2f23f` | 2026-03-30 | Phase 4 Meta-Reasoning — same destructive overwrite |
| `bf3b98b` | 2026-03-30 | A* Search — same destructive overwrite |
| `e88bcb4` | 2026-03-30 | Bug fix identified `public/nexus/` files as corrupt duplicates, **deleted them** |
## Evidence: Code Is Present on Main
All 13 classical AI classes/functions verified present in root `app.js`:
| Class/Function | Line | Status |
|----------------|------|--------|
| `SymbolicEngine` | 82 | ✅ Present |
| `AgentFSM` | 135 | ✅ Present |
| `KnowledgeGraph` | 160 | ✅ Present |
| `Blackboard` | 181 | ✅ Present |
| `SymbolicPlanner` | 210 | ✅ Present |
| `HTNPlanner` | 295 | ✅ Present |
| `CaseBasedReasoner` | 343 | ✅ Present |
| `NeuroSymbolicBridge` | 392 | ✅ Present |
| `MetaReasoningLayer` | 422 | ✅ Present |
| `AdaptiveCalibrator` | 460 | ✅ Present |
| `PSELayer` | 566 | ✅ Present |
| `setupGOFAI()` | 596 | ✅ Present |
| `updateGOFAI()` | 622 | ✅ Present |
| Bitmask fact indexing | 86 | ✅ Present |
| A* search | 231 | ✅ Present |
These were injected by commit `af7a4c4` (PR #775, merged via `a855d54`) into the correct path.
## What Actually Happened
1. Google AI Agent wrote good GOFAI code to root `app.js` via the correct PR (#775)
2. A second wave of Google AI Agent instances also wrote to `public/nexus/app.js` (wrong path)
3. Those `public/nexus/` files kept getting overwritten by subsequent agent commits
4. Commit `e88bcb4` correctly identified the `public/nexus/` files as corrupt and deleted them
5. Alexander interpreted the git log as "classical AI code keeps disappearing"
6. The code was never actually gone — it just lived in root `app.js` the whole time
## Prevention Strategy
1. **Add `public/nexus/` to `.gitignore`** — prevents agents from accidentally writing to the wrong path again
2. **Add canonical path documentation to CLAUDE.md** — any agent reading this repo will know where frontend code lives
3. **This report** — serves as the audit trail so this confusion doesn't recur
## Acceptance Criteria
- [x] Git history audited for classical AI commits
- [x] Found the commits — they exist, code was written to wrong path
- [x] Root cause identified — duplicate agents writing to `public/nexus/` (wrong path)
- [x] Prevention strategy implemented — `.gitignore` + `CLAUDE.md` path guard
- [x] Report filed with findings (this document)

116
app.js
View File

@@ -44,6 +44,7 @@ let particles, dustParticles;
let debugOverlay;
let frameCount = 0, lastFPSTime = 0, fps = 0;
let chatOpen = true;
let memoryFeedEntries = []; // Mnemosyne: recent memory events for feed panel
let loadProgress = 0;
let performanceTier = 'high';
@@ -2041,6 +2042,14 @@ function connectHermes() {
addChatMessage('system', 'Hermes link established.');
updateWsHudStatus(true);
refreshWorkshopPanel();
// Mnemosyne: request memory sync from Hermes
try {
hermesWs.send(JSON.stringify({ type: 'memory', action: 'sync_request' }));
console.info('[Mnemosyne] Sent sync_request to Hermes');
} catch (e) {
console.warn('[Mnemosyne] Failed to send sync_request:', e);
}
};
// Initialize MemPalace
@@ -2091,6 +2100,8 @@ function handleHermesMessage(data) {
recentToolOutputs.push({ type: 'result', agent: data.agent || 'SYSTEM', content });
addToolMessage(data.agent || 'SYSTEM', 'result', content);
refreshWorkshopPanel();
} else if (data.type === 'memory') {
handleMemoryMessage(data);
} else if (data.type === 'history') {
const container = document.getElementById('chat-messages');
container.innerHTML = '';
@@ -2102,6 +2113,111 @@ function handleHermesMessage(data) {
}
}
// ═══════════════════════════════════════════
// MNEMOSYNE — LIVE MEMORY BRIDGE
// ═══════════════════════════════════════════
/**
* Handle incoming memory messages from Hermes WS.
* Actions: place, remove, update, sync_response
*/
/**
* Clear all entries from the memory feed.
*/
function clearMemoryFeed() {
memoryFeedEntries = [];
renderMemoryFeed();
console.info('[Mnemosyne] Memory feed cleared');
}
function handleMemoryMessage(data) {
const action = data.action;
const memory = data.memory;
const memories = data.memories;
if (action === 'place' && memory) {
const placed = SpatialMemory.placeMemory(memory);
if (placed) {
addMemoryFeedEntry('place', memory);
console.info('[Mnemosyne] Memory placed via WS:', memory.id);
}
} else if (action === 'remove' && memory) {
SpatialMemory.removeMemory(memory.id);
addMemoryFeedEntry('remove', memory);
console.info('[Mnemosyne] Memory removed via WS:', memory.id);
} else if (action === 'update' && memory) {
SpatialMemory.updateMemory(memory.id, memory);
addMemoryFeedEntry('update', memory);
console.info('[Mnemosyne] Memory updated via WS:', memory.id);
} else if (action === 'sync_response' && Array.isArray(memories)) {
const count = SpatialMemory.importMemories(memories);
addMemoryFeedEntry('sync', { content: count + ' memories synced', id: 'sync' });
console.info('[Mnemosyne] Synced', count, 'memories from Hermes');
} else {
console.warn('[Mnemosyne] Unknown memory action:', action);
}
}
/**
* Add an entry to the memory activity feed panel.
*/
function addMemoryFeedEntry(action, memory) {
const entry = {
action,
content: memory.content || memory.id || '(unknown)',
category: memory.category || 'working',
timestamp: new Date().toISOString()
};
memoryFeedEntries.unshift(entry);
if (memoryFeedEntries.length > 5) memoryFeedEntries.pop();
renderMemoryFeed();
// Auto-dismiss entries older than 5 minutes
setTimeout(() => {
const idx = memoryFeedEntries.indexOf(entry);
if (idx > -1) {
memoryFeedEntries.splice(idx, 1);
renderMemoryFeed();
}
}, 300000);
}
/**
* Render the memory feed panel.
*/
function renderMemoryFeed() {
const container = document.getElementById('memory-feed-list');
if (!container) return;
container.innerHTML = '';
memoryFeedEntries.forEach(entry => {
const el = document.createElement('div');
el.className = 'memory-feed-entry memory-feed-' + entry.action;
const regionDef = SpatialMemory.REGIONS[entry.category] || SpatialMemory.REGIONS.working;
const dotColor = '#' + regionDef.color.toString(16).padStart(6, '0');
const time = new Date(entry.timestamp).toLocaleTimeString();
const truncated = entry.content.length > 40 ? entry.content.slice(0, 40) + '\u2026' : entry.content;
const actionIcon = { place: '\u2795', remove: '\u2796', update: '\u270F', sync: '\u21C4' }[entry.action] || '\u2022';
el.innerHTML = '<span class="memory-feed-dot" style="background:' + dotColor + '"></span>' +
'<span class="memory-feed-action">' + actionIcon + '</span>' +
'<span class="memory-feed-content">' + truncated + '</span>' +
'<span class="memory-feed-time">' + time + '</span>';
container.appendChild(el);
});
// Show feed if there are entries
const panel = document.getElementById('memory-feed');
if (panel) panel.style.display = memoryFeedEntries.length > 0 ? 'block' : 'none';
}
function updateWsHudStatus(connected) {
// Update MemPalace status alongside regular WS status
updateMemPalaceStatus();

View File

@@ -439,5 +439,15 @@ index.html
setInterval(poll, INTERVAL);
})();
</script>
<!-- Memory Activity Feed (Mnemosyne) -->
<div id="memory-feed" class="memory-feed" style="display:none;">
<div class="memory-feed-header">
<span class="memory-feed-title">✨ Memory Feed</span>
<div class="memory-feed-actions"><button class="memory-feed-clear" onclick="clearMemoryFeed()">Clear</button><button class="memory-feed-toggle" onclick="document.getElementById('memory-feed').style.display='none'"></button></div>
</div>
<div id="memory-feed-list" class="memory-feed-list"></div>
</div>
</body>
</html>

View File

@@ -471,8 +471,83 @@ const SpatialMemory = (() => {
return results.slice(0, maxResults);
}
// ─── BULK IMPORT (WebSocket sync) ───────────────────
/**
* Import an array of memories in batch — for WebSocket sync.
* Skips duplicates (same id). Returns count of newly placed.
* @param {Array} memories - Array of memory objects { id, content, category, ... }
* @returns {number} Count of newly placed memories
*/
function importMemories(memories) {
if (!Array.isArray(memories) || memories.length === 0) return 0;
let count = 0;
memories.forEach(mem => {
if (mem.id && !_memoryObjects[mem.id]) {
placeMemory(mem);
count++;
}
});
if (count > 0) {
_dirty = true;
saveToStorage();
console.info('[Mnemosyne] Bulk imported', count, 'new memories (total:', Object.keys(_memoryObjects).length, ')');
}
return count;
}
// ─── UPDATE MEMORY ──────────────────────────────────
/**
* Update an existing memory's visual properties (strength, connections).
* Does not move the crystal — only updates metadata and re-renders.
* @param {string} memId - Memory ID to update
* @param {object} updates - Fields to update: { strength, connections, content }
* @returns {boolean} True if updated
*/
function updateMemory(memId, updates) {
const obj = _memoryObjects[memId];
if (!obj) return false;
if (updates.strength != null) {
const strength = Math.max(0.05, Math.min(1, updates.strength));
obj.mesh.userData.strength = strength;
obj.mesh.material.emissiveIntensity = 1.5 * strength;
obj.mesh.material.opacity = 0.5 + strength * 0.4;
}
if (updates.content != null) {
obj.data.content = updates.content;
}
if (updates.connections != null) {
obj.data.connections = updates.connections;
// Rebuild connection lines
_rebuildConnections(memId);
}
_dirty = true;
saveToStorage();
return true;
}
function _rebuildConnections(memId) {
// Remove existing lines for this memory
for (let i = _connectionLines.length - 1; i >= 0; i--) {
const line = _connectionLines[i];
if (line.userData.from === memId || line.userData.to === memId) {
if (line.parent) line.parent.remove(line);
line.geometry.dispose();
line.material.dispose();
_connectionLines.splice(i, 1);
}
}
// Recreate lines for current connections
const obj = _memoryObjects[memId];
if (!obj || !obj.data.connections) return;
obj.data.connections.forEach(targetId => {
const target = _memoryObjects[targetId];
if (target) _createConnectionLine(obj, target);
});
}
return {
init, placeMemory, removeMemory, update,
init, placeMemory, removeMemory, update, importMemories, updateMemory,
getMemoryAtPosition, getRegionAtPosition, getMemoriesInRegion, getAllMemories,
exportIndex, importIndex, searchNearby, REGIONS,
saveToStorage, loadFromStorage, clearStorage

121
style.css
View File

@@ -1223,3 +1223,124 @@ canvas#nexus-canvas {
.l402-msg { color: #fff; }
.pse-status { color: #4af0c0; font-weight: 600; }
/* ═══════════════════════════════════════════
MNEMOSYNE — Memory Activity Feed
═══════════════════════════════════════════ */
.memory-feed {
position: fixed;
bottom: 20px;
left: 20px;
width: 320px;
background: rgba(10, 15, 40, 0.92);
border: 1px solid rgba(74, 240, 192, 0.25);
border-radius: 10px;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
z-index: 900;
font-family: 'JetBrains Mono', monospace;
animation: memoryFeedIn 0.3s ease-out;
}
@keyframes memoryFeedIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.memory-feed-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
border-bottom: 1px solid rgba(74, 240, 192, 0.15);
}
.memory-feed-title {
color: #4af0c0;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.5px;
}
.memory-feed-actions {
display: flex;
gap: 8px;
align-items: center;
}
.memory-feed-clear {
background: rgba(74, 240, 192, 0.1);
border: 1px solid rgba(74, 240, 192, 0.3);
color: #4af0c0;
font-size: 10px;
padding: 2px 6px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
font-family: inherit;
}
.memory-feed-clear:hover {
background: rgba(74, 240, 192, 0.2);
border-color: #4af0c0;
}
.memory-feed-toggle {
background: none;
border: none;
color: rgba(255, 255, 255, 0.4);
cursor: pointer;
font-size: 14px;
padding: 0 4px;
}
.memory-feed-toggle:hover { color: #fff; }
.memory-feed-list {
max-height: 200px;
overflow-y: auto;
padding: 6px 0;
}
.memory-feed-entry {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
font-size: 11px;
color: rgba(255, 255, 255, 0.75);
transition: background 0.2s;
}
.memory-feed-entry:hover {
background: rgba(255, 255, 255, 0.05);
}
.memory-feed-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.memory-feed-action {
flex-shrink: 0;
font-size: 12px;
}
.memory-feed-content {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.memory-feed-time {
flex-shrink: 0;
color: rgba(255, 255, 255, 0.3);
font-size: 10px;
}
.memory-feed-place { border-left: 2px solid #4af0c0; }
.memory-feed-remove { border-left: 2px solid #ff4466; }
.memory-feed-update { border-left: 2px solid #ffd700; }
.memory-feed-sync { border-left: 2px solid #7b5cff; }