diff --git a/nexus/symbolic-engine.js b/nexus/symbolic-engine.js new file mode 100644 index 00000000..2228943a --- /dev/null +++ b/nexus/symbolic-engine.js @@ -0,0 +1,377 @@ + +export class SymbolicEngine { + constructor() { + this.facts = new Map(); + this.factIndices = new Map(); + this.factMask = 0n; + this.rules = []; + this.reasoningLog = []; + } + + addFact(key, value) { + this.facts.set(key, value); + if (!this.factIndices.has(key)) { + this.factIndices.set(key, BigInt(this.factIndices.size)); + } + const bitIndex = this.factIndices.get(key); + if (value) { + this.factMask |= (1n << bitIndex); + } else { + this.factMask &= ~(1n << bitIndex); + } + } + + addRule(condition, action, description) { + this.rules.push({ condition, action, description }); + } + + reason() { + this.rules.forEach(rule => { + if (rule.condition(this.facts)) { + const result = rule.action(this.facts); + if (result) { + this.logReasoning(rule.description, result); + } + } + }); + } + + logReasoning(ruleDesc, outcome) { + const entry = { timestamp: Date.now(), rule: ruleDesc, outcome: outcome }; + this.reasoningLog.unshift(entry); + if (this.reasoningLog.length > 5) this.reasoningLog.pop(); + + const container = document.getElementById('symbolic-log-content'); + if (container) { + const logDiv = document.createElement('div'); + logDiv.className = 'symbolic-log-entry'; + logDiv.innerHTML = `[RULE] ${ruleDesc}→ ${outcome}`; + container.prepend(logDiv); + if (container.children.length > 5) container.lastElementChild.remove(); + } + } +} + +export class AgentFSM { + constructor(agentId, initialState) { + this.agentId = agentId; + this.state = initialState; + this.transitions = {}; + } + + addTransition(fromState, toState, condition) { + if (!this.transitions[fromState]) this.transitions[fromState] = []; + this.transitions[fromState].push({ toState, condition }); + } + + update(facts) { + const possibleTransitions = this.transitions[this.state] || []; + for (const transition of possibleTransitions) { + if (transition.condition(facts)) { + console.log(`[FSM] Agent ${this.agentId} transitioning: ${this.state} -> ${transition.toState}`); + this.state = transition.toState; + return true; + } + } + return false; + } +} + +export class KnowledgeGraph { + constructor() { + this.nodes = new Map(); + this.edges = []; + } + + addNode(id, type, metadata = {}) { + this.nodes.set(id, { id, type, ...metadata }); + } + + addEdge(from, to, relation) { + this.edges.push({ from, to, relation }); + } + + query(from, relation) { + return this.edges + .filter(e => e.from === from && e.relation === relation) + .map(e => this.nodes.get(e.to)); + } +} + +export class Blackboard { + constructor() { + this.data = {}; + this.subscribers = []; + } + + write(key, value, source) { + const oldValue = this.data[key]; + this.data[key] = value; + this.notify(key, value, oldValue, source); + } + + read(key) { return this.data[key]; } + + subscribe(callback) { this.subscribers.push(callback); } + + notify(key, value, oldValue, source) { + this.subscribers.forEach(sub => sub(key, value, oldValue, source)); + const container = document.getElementById('blackboard-log-content'); + if (container) { + const entry = document.createElement('div'); + entry.className = 'blackboard-entry'; + entry.innerHTML = `[${source}] ${key}: ${JSON.stringify(value)}`; + container.prepend(entry); + if (container.children.length > 8) container.lastElementChild.remove(); + } + } +} + +export class SymbolicPlanner { + constructor() { + this.actions = []; + this.currentPlan = []; + } + + addAction(name, preconditions, effects) { + this.actions.push({ name, preconditions, effects }); + } + + heuristic(state, goal) { + let h = 0; + for (let key in goal) { + if (state[key] !== goal[key]) { + h += Math.abs((state[key] || 0) - (goal[key] || 0)); + } + } + return h; + } + + findPlan(initialState, goalState) { + let openSet = [{ state: initialState, plan: [], g: 0, h: this.heuristic(initialState, goalState) }]; + let visited = new Map(); + visited.set(JSON.stringify(initialState), 0); + + while (openSet.length > 0) { + openSet.sort((a, b) => (a.g + a.h) - (b.g + b.h)); + let { state, plan, g } = openSet.shift(); + + if (this.isGoalReached(state, goalState)) return plan; + + for (let action of this.actions) { + if (this.arePreconditionsMet(state, action.preconditions)) { + let nextState = { ...state, ...action.effects }; + let stateStr = JSON.stringify(nextState); + let nextG = g + 1; + + if (!visited.has(stateStr) || nextG < visited.get(stateStr)) { + visited.set(stateStr, nextG); + openSet.push({ + state: nextState, + plan: [...plan, action.name], + g: nextG, + h: this.heuristic(nextState, goalState) + }); + } + } + } + } + return null; + } + + isGoalReached(state, goal) { + for (let key in goal) { + if (state[key] !== goal[key]) return false; + } + return true; + } + + arePreconditionsMet(state, preconditions) { + for (let key in preconditions) { + if (state[key] < preconditions[key]) return false; + } + return true; + } + + logPlan(plan) { + this.currentPlan = plan; + const container = document.getElementById('planner-log-content'); + if (container) { + container.innerHTML = ''; + if (!plan || plan.length === 0) { + container.innerHTML = '