diff --git a/docs/symbolic-debugger.md b/docs/symbolic-debugger.md
new file mode 100644
index 00000000..de0402b0
--- /dev/null
+++ b/docs/symbolic-debugger.md
@@ -0,0 +1,71 @@
+# GOFAI Symbolic Engine Debugger Overlay
+
+Refs: the-nexus #871
+
+## Overview
+
+A specialized debug overlay that shows the internal state of the Symbolic Engine in real-time. Press **Ctrl+Shift+G** to toggle.
+
+## Features
+
+### 1. Active Symbols Panel
+Displays all facts currently in the symbolic engine with their truth values:
+- Green ● = true
+- Red ○ = false
+
+### 2. FSM States Panel
+Shows the current state of all registered finite state machines:
+- Agent ID on the left
+- Current state on the right (highlighted)
+
+### 3. Reasoning Paths Panel
+Chronological log of all reasoning steps:
+- Timestamp
+- Rule that fired
+- Outcome produced
+
+### 4. Knowledge Graph Panel
+- Node count and edge count
+- Mini visualization of graph topology (up to 15 nodes)
+- Color-coded by type (Agent, Location, etc.)
+
+### 5. Performance Metrics
+- Number of facts
+- Number of rules
+- JavaScript heap usage (if available)
+
+## Usage
+
+```javascript
+import { SymbolicDebugger } from './nexus/components/symbolic-debugger.js';
+import { SymbolicEngine } from './nexus/symbolic-engine.js';
+
+// Create engine
+const engine = new SymbolicEngine();
+
+// Initialize debugger
+SymbolicDebugger.init({
+ engine: engine,
+ fsmRegistry: new Map(), // optional
+ knowledgeGraph: null // optional
+});
+
+// Show the overlay
+SymbolicDebugger.show();
+
+// Or toggle with Ctrl+Shift+G
+```
+
+## Auto-refresh
+
+The debugger updates automatically every second when visible. Manual refresh via the ↻ button.
+
+## Dragging
+
+The overlay can be dragged by its header to reposition on screen.
+
+## Testing
+
+```bash
+node tests/test_symbolic_debugger.js
+```
diff --git a/nexus/components/symbolic-debugger.js b/nexus/components/symbolic-debugger.js
new file mode 100644
index 00000000..9eb9747b
--- /dev/null
+++ b/nexus/components/symbolic-debugger.js
@@ -0,0 +1,506 @@
+// ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════
+// GOFAI Symbolic Engine Debugger Overlay (issue #871)
+// ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════
+//
+// Specialized debug overlay showing internal state of the Symbolic Engine:
+// — Active symbols and their truth values
+// — Reasoning paths with timestamps
+// — FSM state visualizations
+// — Knowledge graph topology
+// — Performance metrics
+//
+// Usage:
+// import { SymbolicDebugger } from './symbolic-debugger.js';
+// SymbolicDebugger.init({ engine, blackboard, fsmRegistry });
+// SymbolicDebugger.show();
+// SymbolicDebugger.hide();
+// SymbolicDebugger.update(); // refresh from current state
+// ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
+
+const SymbolicDebugger = (() => {
+ let _overlay = null;
+ let _engine = null;
+ let _blackboard = null;
+ let _fsmRegistry = null;
+ let _kg = null;
+ let _visible = false;
+ let _refreshInterval = null;
+
+ // ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+
+ function init(opts = {}) {
+ _engine = opts.engine || null;
+ _blackboard = opts.blackboard || null;
+ _fsmRegistry = opts.fsmRegistry || null;
+ _kg = opts.knowledgeGraph || null;
+
+ // Create overlay if not exists
+ if (!document.getElementById('symbolic-debugger-overlay')) {
+ _createOverlay();
+ }
+ _overlay = document.getElementById('symbolic-debugger-overlay');
+
+ // Keyboard shortcut: Ctrl+Shift+G to toggle
+ document.addEventListener('keydown', (e) => {
+ if (e.ctrlKey && e.shiftKey && e.key === 'G') {
+ toggle();
+ }
+ });
+
+ console.log('[SymbolicDebugger] Initialized. Press Ctrl+Shift+G to toggle.');
+ }
+
+ // ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+
+ function _createOverlay() {
+ const div = document.createElement('div');
+ div.id = 'symbolic-debugger-overlay';
+ div.className = 'sym-debug-overlay';
+ div.style.cssText = `
+ position: fixed;
+ top: 60px;
+ right: 20px;
+ width: 480px;
+ max-height: calc(100vh - 80px);
+ background: rgba(10, 15, 30, 0.95);
+ border: 1px solid #4af0c0;
+ border-radius: 4px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 12px;
+ color: #e0f0ff;
+ overflow-y: auto;
+ z-index: 9999;
+ display: none;
+ box-shadow: 0 0 20px rgba(74, 240, 192, 0.3);
+ `;
+
+ div.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(div);
+
+ // Event listeners
+ document.getElementById('sym-debug-close').onclick = hide;
+ document.getElementById('sym-debug-refresh').onclick = update;
+
+ // Make draggable
+ let isDragging = false;
+ let dragOffsetX = 0;
+ let dragOffsetY = 0;
+
+ const header = div.querySelector('.sym-debug-header');
+ header.addEventListener('mousedown', (e) => {
+ isDragging = true;
+ dragOffsetX = e.clientX - div.offsetLeft;
+ dragOffsetY = e.clientY - div.offsetTop;
+ });
+
+ document.addEventListener('mousemove', (e) => {
+ if (!isDragging) return;
+ div.style.left = (e.clientX - dragOffsetX) + 'px';
+ div.style.top = (e.clientY - dragOffsetY) + 'px';
+ div.style.right = 'auto';
+ });
+
+ document.addEventListener('mouseup', () => {
+ isDragging = false;
+ });
+ }
+
+ // ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+
+ function update() {
+ if (!_visible || !_overlay) return;
+
+ // Update symbols
+ _updateSymbols();
+
+ // Update FSM states
+ _updateFSM();
+
+ // Update reasoning log
+ _updateReasoning();
+
+ // Update knowledge graph
+ _updateKnowledgeGraph();
+
+ // Update metrics
+ _updateMetrics();
+ }
+
+ function _updateSymbols() {
+ const container = document.getElementById('sym-debug-symbols');
+ if (!container || !_engine) {
+ if (container) container.innerHTML = 'No engine connected';
+ return;
+ }
+
+ let html = '';
+ if (_engine.facts && _engine.facts.size > 0) {
+ for (const [key, value] of _engine.facts) {
+ const truthColor = value ? '#4af0c0' : '#ff4466';
+ const truthIcon = value ? '●' : '○';
+ html += `
+ ${_esc(key)}
+ ${truthIcon} ${_esc(String(value))}
+ `;
+ }
+ } else {
+ html = 'No active symbols';
+ }
+ container.innerHTML = html;
+ }
+
+ function _updateFSM() {
+ const container = document.getElementById('sym-debug-fsm');
+ if (!container) return;
+
+ let html = '';
+ if (_fsmRegistry && _fsmRegistry.size > 0) {
+ for (const [agentId, fsm] of _fsmRegistry) {
+ const transitions = fsm.transitions ? Object.keys(fsm.transitions).length : 0;
+ html += `
+
+ ${_esc(agentId)}
+ ${_esc(fsm.state)}
+
+ `;
+ }
+ } else if (_engine && _engine.fsm) {
+ // Single FSM mode
+ html += `
+
+ Primary FSM
+ ${_esc(_engine.fsm.state)}
+
+ `;
+ } else {
+ html = 'No FSM registered';
+ }
+ container.innerHTML = html;
+ }
+
+ function _updateReasoning() {
+ const container = document.getElementById('sym-debug-reasoning');
+ if (!container || !_engine) {
+ if (container) container.innerHTML = 'No reasoning log';
+ return;
+ }
+
+ let html = '';
+ if (_engine.reasoningLog && _engine.reasoningLog.length > 0) {
+ for (const entry of _engine.reasoningLog) {
+ const time = entry.timestamp
+ ? new Date(entry.timestamp).toLocaleTimeString()
+ : '--:--:--';
+ html += `
+
+
${time}
+
${_esc(entry.rule || 'Unknown rule')}
+
→ ${_esc(entry.outcome || 'No outcome')}
+
+ `;
+ }
+ } else {
+ html = 'No reasoning paths recorded';
+ }
+ container.innerHTML = html;
+ }
+
+ function _updateKnowledgeGraph() {
+ const statsContainer = document.getElementById('sym-debug-kg-stats');
+ const vizContainer = document.getElementById('sym-debug-kg-viz');
+ if (!statsContainer || !vizContainer) return;
+
+ if (!_kg) {
+ statsContainer.innerHTML = 'No knowledge graph connected';
+ vizContainer.innerHTML = '';
+ return;
+ }
+
+ const nodeCount = _kg.nodes ? _kg.nodes.size : 0;
+ const edgeCount = _kg.edges ? _kg.edges.length : 0;
+
+ statsContainer.innerHTML = `
+ ${nodeCount} nodes
+ |
+ ${edgeCount} edges
+ `;
+
+ // Simple visualization
+ if (nodeCount > 0 && vizContainer) {
+ let vizHtml = '';
+ let idx = 0;
+ for (const [nodeId, node] of _kg.nodes) {
+ const x = 20 + (idx % 5) * 80;
+ const y = 20 + Math.floor(idx / 5) * 30;
+ const color = node.type === 'Agent' ? '#4af0c0' :
+ node.type === 'Location' ? '#ffaa22' : '#4488ff';
+ vizHtml += `
+
+ `;
+ idx++;
+ if (idx >= 15) break; // Limit visualization
+ }
+ vizContainer.innerHTML = vizHtml;
+ } else {
+ vizContainer.innerHTML = '';
+ }
+ }
+
+ function _updateMetrics() {
+ const container = document.getElementById('sym-debug-metrics');
+ if (!container) return;
+
+ let html = '';
+
+ // Engine metrics
+ if (_engine) {
+ const factCount = _engine.facts ? _engine.facts.size : 0;
+ const ruleCount = _engine.rules ? _engine.rules.length : 0;
+ html += `
+ Facts: ${factCount}
+ Rules: ${ruleCount}
+ `;
+ }
+
+ // Memory usage (rough estimate)
+ if (performance && performance.memory) {
+ const usedMB = Math.round(performance.memory.usedJSHeapSize / 1048576);
+ html += `
+ Heap: ${usedMB} MB
+ `;
+ }
+
+ container.innerHTML = html || 'No metrics available';
+ }
+
+ // ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+
+ function show() {
+ if (!_overlay) return;
+ _overlay.style.display = 'block';
+ _visible = true;
+ update();
+ _startAutoRefresh();
+ }
+
+ function hide() {
+ if (!_overlay) return;
+ _overlay.style.display = 'none';
+ _visible = false;
+ _stopAutoRefresh();
+ }
+
+ function toggle() {
+ if (_visible) hide();
+ else show();
+ }
+
+ function isVisible() {
+ return _visible;
+ }
+
+ function _startAutoRefresh() {
+ if (_refreshInterval) return;
+ _refreshInterval = setInterval(update, 1000); // Update every second
+ }
+
+ function _stopAutoRefresh() {
+ if (_refreshInterval) {
+ clearInterval(_refreshInterval);
+ _refreshInterval = null;
+ }
+ }
+
+ function _esc(str) {
+ if (typeof str !== 'string') return String(str);
+ return str
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"');
+ }
+
+ // ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+
+ return {
+ init,
+ show,
+ hide,
+ toggle,
+ isVisible,
+ update,
+ };
+})();
+
+// Auto-export for module or global usage
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = { SymbolicDebugger };
+} else if (typeof window !== 'undefined') {
+ window.SymbolicDebugger = SymbolicDebugger;
+}
diff --git a/tests/test_symbolic_debugger.js b/tests/test_symbolic_debugger.js
new file mode 100644
index 00000000..fe44001e
--- /dev/null
+++ b/tests/test_symbolic_debugger.js
@@ -0,0 +1,95 @@
+/**
+ * Tests for SymbolicDebugger component (issue #871)
+ */
+
+import { SymbolicDebugger } from '../nexus/components/symbolic-debugger.js';
+import { SymbolicEngine, AgentFSM, KnowledgeGraph } from '../nexus/symbolic-engine.js';
+
+// Mock DOM for Node.js testing
+if (typeof document === 'undefined') {
+ const mockElements = new Map();
+
+ global.document = {
+ createElement: (tag) => {
+ const el = {
+ tagName: tag,
+ style: {},
+ innerHTML: '',
+ children: [],
+ appendChild: function(child) { this.children.push(child); return child; },
+ prepend: function(child) { this.children.unshift(child); return child; },
+ querySelector: () => null,
+ querySelectorAll: () => [],
+ addEventListener: () => {},
+ setAttribute: () => {},
+ getAttribute: () => null,
+ };
+ return el;
+ },
+ body: {
+ appendChild: (el) => {
+ mockElements.set(el.id, el);
+ return el;
+ }
+ },
+ addEventListener: () => {},
+ getElementById: (id) => mockElements.get(id) || {
+ style: {},
+ innerHTML: '',
+ onclick: null,
+ addEventListener: () => {},
+ },
+ };
+ global.window = { SymbolicDebugger: null };
+}
+
+function assert(condition, message) {
+ if (!condition) {
+ console.error(`❌ FAILED: ${message}`);
+ process.exit(1);
+ }
+ console.log(`✔ PASSED: ${message}`);
+}
+
+console.log('--- Running Symbolic Debugger Tests ---');
+
+// Test 1: Module exports
+assert(typeof SymbolicDebugger === 'object', 'SymbolicDebugger exports an object');
+assert(typeof SymbolicDebugger.init === 'function', 'SymbolicDebugger has init method');
+assert(typeof SymbolicDebugger.show === 'function', 'SymbolicDebugger has show method');
+assert(typeof SymbolicDebugger.hide === 'function', 'SymbolicDebugger has hide method');
+assert(typeof SymbolicDebugger.toggle === 'function', 'SymbolicDebugger has toggle method');
+assert(typeof SymbolicDebugger.update === 'function', 'SymbolicDebugger has update method');
+
+// Test 2: Initial state
+assert(SymbolicDebugger.isVisible() === false, 'Debugger starts hidden');
+
+// Test 3: Engine integration (mock)
+const mockEngine = {
+ facts: new Map([['energy', 75], ['stable', true]]),
+ rules: [{ condition: () => true, action: () => 'test' }],
+ reasoningLog: [
+ { timestamp: Date.now(), rule: 'TestRule', outcome: 'TestOutcome' }
+ ]
+};
+
+SymbolicDebugger.init({ engine: mockEngine });
+assert(true, 'Debugger initializes with engine');
+
+// Test 4: FSM integration
+const mockFSM = { state: 'IDLE', transitions: { IDLE: [] } };
+SymbolicDebugger.init({ engine: mockEngine, fsmRegistry: new Map([['Agent1', mockFSM]]) });
+assert(true, 'Debugger initializes with FSM registry');
+
+// Test 5: Knowledge Graph integration
+const mockKG = {
+ nodes: new Map([
+ ['A', { id: 'A', type: 'Agent' }],
+ ['B', { id: 'B', type: 'Location' }]
+ ]),
+ edges: [{ from: 'A', to: 'B', relation: 'AT' }]
+};
+SymbolicDebugger.init({ engine: mockEngine, knowledgeGraph: mockKG });
+assert(true, 'Debugger initializes with Knowledge Graph');
+
+console.log('--- All Symbolic Debugger Tests Passed ---');