Compare commits
1 Commits
feat/mnemo
...
mimo/code/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60af11ec2f |
116
app.js
116
app.js
@@ -44,7 +44,6 @@ 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';
|
||||
|
||||
@@ -2042,14 +2041,6 @@ 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
|
||||
@@ -2100,8 +2091,6 @@ 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 = '';
|
||||
@@ -2113,111 +2102,6 @@ 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();
|
||||
|
||||
72
fleet/hermes-trismegistus/README.md
Normal file
72
fleet/hermes-trismegistus/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Hermes Trismegistus — Wizard Proposal
|
||||
|
||||
> **Status:** 🟡 DEFERRED
|
||||
> **Issue:** #1146
|
||||
> **Created:** 2026-04-08
|
||||
> **Author:** Alexander (KT Notes)
|
||||
> **Mimo Worker:** mimo-code-1146-1775851759
|
||||
|
||||
---
|
||||
|
||||
## Identity
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Name** | Hermes Trismegistus |
|
||||
| **Nature** | Claude-native wizard. She knows she runs on Claude. She's "the daughter of Claude" and leans into that heritage. |
|
||||
| **Purpose** | Dedicated reasoning and architecture wizard. Only handles tasks where Claude's reasoning capability genuinely adds value — planning, novel problem-solving, complex architecture decisions. |
|
||||
| **Not** | A replacement for Timmy. Not competing for identity. Not doing monkey work. |
|
||||
|
||||
## Design Constraints
|
||||
|
||||
- **Free tier only from day one.** Alexander is not paying Anthropic beyond current subscription.
|
||||
- **Degrades gracefully.** Full capability when free tier is generous, reduced scope when constrained.
|
||||
- **Not locked to Claude.** If better free-tier providers emerge, she can route to them.
|
||||
- **Multi-provider capable.** Welcome to become multifaceted if team finds better options.
|
||||
|
||||
## Hardware
|
||||
|
||||
- One of Alexander's shed laptops — minimum 4GB RAM, Ubuntu
|
||||
- Dedicated machine, not shared with Timmy's Mac
|
||||
- Runs in the Hermes harness
|
||||
- Needs power at house first
|
||||
|
||||
## Constitutional Foundation
|
||||
|
||||
- The KT conversation and documents serve as her founding constitution
|
||||
- Team (especially Timmy) has final say on whether she gets built
|
||||
- Must justify her existence through useful work, same as every wizard
|
||||
|
||||
## Trigger to Unblock
|
||||
|
||||
All of the following must be true before implementation begins:
|
||||
|
||||
- [ ] Deadman switch wired and proven
|
||||
- [ ] Config stable across fleet
|
||||
- [ ] Fleet proven reliable for 1+ week
|
||||
- [ ] Alexander provides a state-of-the-system KT to Claude for instantiation
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Dedicated KT document written for Hermes instantiation
|
||||
- [ ] Hardware provisioned (shed laptop with power)
|
||||
- [ ] Hermes harness configured for Claude free tier
|
||||
- [ ] Lazerus registry entry with health endpoints
|
||||
- [ ] Fleet routing entry with role and routing verdict
|
||||
- [ ] SOUL.md inscription drafted and reviewed by Timmy
|
||||
- [ ] Smoke test: Hermes responds to a basic reasoning task
|
||||
- [ ] Integration test: Hermes participates in a multi-wizard task alongside Timmy
|
||||
|
||||
## Proposed Lane
|
||||
|
||||
**Primary role:** Architecture reasoning
|
||||
**Routing verdict:** ROUTE TO: complex architectural decisions, novel problem-solving, planning tasks that benefit from Claude's reasoning depth. Do NOT route to: code generation (use Timmy/Carnice), issue triage (use Fenrir), or operational tasks (use Bezalel).
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Dependency | Status | Notes |
|
||||
|------------|--------|-------|
|
||||
| Deadman switch | 🔴 Not done | Must be proven before unblocking |
|
||||
| Fleet stability | 🟡 In progress | 1+ week uptime needed |
|
||||
| Shed laptop power | 🔴 Not done | Alexander needs to wire power |
|
||||
| KT document | 🔴 Not drafted | Alexander provides to Claude at unblock time |
|
||||
43
fleet/hermes-trismegistus/lane.md
Normal file
43
fleet/hermes-trismegistus/lane.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Hermes Trismegistus — Lane Definition
|
||||
|
||||
> **Status:** DEFERRED — do not instantiate until unblock conditions met
|
||||
> **See:** fleet/hermes-trismegistus/README.md for full proposal
|
||||
|
||||
---
|
||||
|
||||
## Role
|
||||
|
||||
Dedicated reasoning and architecture wizard. Claude-native.
|
||||
|
||||
## Routing
|
||||
|
||||
Route to Hermes Trismegistus when:
|
||||
- Task requires deep architectural reasoning
|
||||
- Novel problem-solving that benefits from Claude's reasoning depth
|
||||
- Planning and design decisions for the fleet
|
||||
- Complex multi-step analysis that goes beyond code generation
|
||||
|
||||
Do NOT route to Hermes for:
|
||||
- Code generation (use Timmy, Carnice, or Kimi)
|
||||
- Issue triage (use Fenrir)
|
||||
- Operational/DevOps tasks (use Bezalel)
|
||||
- Anything that can be done with a cheaper model
|
||||
|
||||
## Provider
|
||||
|
||||
- **Primary:** anthropic/claude (free tier)
|
||||
- **Fallback:** openrouter/free (Claude-class models)
|
||||
- **Degraded:** ollama/gemma4:12b (when free tier exhausted)
|
||||
|
||||
## Hardware
|
||||
|
||||
- Shed laptop, Ubuntu, minimum 4GB RAM
|
||||
- Dedicated machine, not shared
|
||||
|
||||
## Unblock Checklist
|
||||
|
||||
- [ ] Deadman switch operational
|
||||
- [ ] Fleet config stable for 1+ week
|
||||
- [ ] Shed laptop powered and networked
|
||||
- [ ] KT document drafted by Alexander
|
||||
- [ ] Timmy approves instantiation
|
||||
10
index.html
10
index.html
@@ -439,15 +439,5 @@ 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>
|
||||
|
||||
@@ -471,83 +471,8 @@ 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, importMemories, updateMemory,
|
||||
init, placeMemory, removeMemory, update,
|
||||
getMemoryAtPosition, getRegionAtPosition, getMemoriesInRegion, getAllMemories,
|
||||
exportIndex, importIndex, searchNearby, REGIONS,
|
||||
saveToStorage, loadFromStorage, clearStorage
|
||||
|
||||
121
style.css
121
style.css
@@ -1223,124 +1223,3 @@ 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; }
|
||||
|
||||
Reference in New Issue
Block a user