feat: extract symbolic engine components
This commit is contained in:
377
nexus/symbolic-engine.js
Normal file
377
nexus/symbolic-engine.js
Normal file
@@ -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 = `<span class=\symbolic-rule\>[RULE] ${ruleDesc}</span><span class=\symbolic-outcome\>→ ${outcome}</span>`;
|
||||
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 = `<span class=\bb-source\>[${source}]</span> <span class=\bb-key\>${key}</span>: <span class=\bb-value\>${JSON.stringify(value)}</span>`;
|
||||
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 = '<div class=\planner-empty\>NO ACTIVE PLAN</div>';
|
||||
return;
|
||||
}
|
||||
plan.forEach((step, i) => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'planner-step';
|
||||
div.innerHTML = `<span class=\step-num\>${i+1}.</span> ${step}`;
|
||||
container.appendChild(div);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class HTNPlanner {
|
||||
constructor() {
|
||||
this.methods = {};
|
||||
this.primitiveTasks = {};
|
||||
}
|
||||
|
||||
addMethod(taskName, preconditions, subtasks) {
|
||||
if (!this.methods[taskName]) this.methods[taskName] = [];
|
||||
this.methods[taskName].push({ preconditions, subtasks });
|
||||
}
|
||||
|
||||
addPrimitiveTask(taskName, preconditions, effects) {
|
||||
this.primitiveTasks[taskName] = { preconditions, effects };
|
||||
}
|
||||
|
||||
findPlan(initialState, tasks) {
|
||||
return this.decompose(initialState, tasks, []);
|
||||
}
|
||||
|
||||
decompose(state, tasks, plan) {
|
||||
if (tasks.length === 0) return plan;
|
||||
const [task, ...remainingTasks] = tasks;
|
||||
if (this.primitiveTasks[task]) {
|
||||
const { preconditions, effects } = this.primitiveTasks[task];
|
||||
if (this.arePreconditionsMet(state, preconditions)) {
|
||||
const nextState = { ...state, ...effects };
|
||||
return this.decompose(nextState, remainingTasks, [...plan, task]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const methods = this.methods[task] || [];
|
||||
for (const method of methods) {
|
||||
if (this.arePreconditionsMet(state, method.preconditions)) {
|
||||
const result = this.decompose(state, [...method.subtasks, ...remainingTasks], plan);
|
||||
if (result) return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
arePreconditionsMet(state, preconditions) {
|
||||
for (const key in preconditions) {
|
||||
if (state[key] < (preconditions[key] || 0)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class CaseBasedReasoner {
|
||||
constructor() {
|
||||
this.caseLibrary = [];
|
||||
}
|
||||
|
||||
addCase(situation, action, outcome) {
|
||||
this.caseLibrary.push({ situation, action, outcome, timestamp: Date.now() });
|
||||
}
|
||||
|
||||
findSimilarCase(currentSituation) {
|
||||
let bestMatch = null;
|
||||
let maxSimilarity = -1;
|
||||
this.caseLibrary.forEach(c => {
|
||||
let similarity = this.calculateSimilarity(currentSituation, c.situation);
|
||||
if (similarity > maxSimilarity) {
|
||||
maxSimilarity = similarity;
|
||||
bestMatch = c;
|
||||
}
|
||||
});
|
||||
return maxSimilarity > 0.7 ? bestMatch : null;
|
||||
}
|
||||
|
||||
calculateSimilarity(s1, s2) {
|
||||
let score = 0, total = 0;
|
||||
for (let key in s1) {
|
||||
if (s2[key] !== undefined) {
|
||||
score += 1 - Math.abs(s1[key] - s2[key]);
|
||||
total += 1;
|
||||
}
|
||||
}
|
||||
return total > 0 ? score / total : 0;
|
||||
}
|
||||
|
||||
logCase(c) {
|
||||
const container = document.getElementById('cbr-log-content');
|
||||
if (container) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'cbr-entry';
|
||||
div.innerHTML = `
|
||||
<div class=\cbr-match\>SIMILAR CASE FOUND (${(this.calculateSimilarity(symbolicEngine.facts, c.situation) * 100).toFixed(0)}%)</div>
|
||||
<div class=\cbr-action\>SUGGESTED: ${c.action}</div>
|
||||
<div class=\cbr-outcome\>PREVIOUS OUTCOME: ${c.outcome}</div>
|
||||
`;
|
||||
container.prepend(div);
|
||||
if (container.children.length > 3) container.lastElementChild.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class NeuroSymbolicBridge {
|
||||
constructor(symbolicEngine, blackboard) {
|
||||
this.engine = symbolicEngine;
|
||||
this.blackboard = blackboard;
|
||||
this.perceptionLog = [];
|
||||
}
|
||||
|
||||
perceive(rawState) {
|
||||
const concepts = [];
|
||||
if (rawState.stability < 0.4 && rawState.energy > 60) concepts.push('UNSTABLE_OSCILLATION');
|
||||
if (rawState.energy < 30 && rawState.activePortals > 2) concepts.push('CRITICAL_DRAIN_PATTERN');
|
||||
concepts.forEach(concept => {
|
||||
this.engine.addFact(concept, true);
|
||||
this.logPerception(concept);
|
||||
});
|
||||
return concepts;
|
||||
}
|
||||
|
||||
logPerception(concept) {
|
||||
const container = document.getElementById('neuro-bridge-log-content');
|
||||
if (container) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'neuro-bridge-entry';
|
||||
div.innerHTML = `<span class=\neuro-icon\>🧠</span> <span class=\neuro-concept\>${concept}</span>`;
|
||||
container.prepend(div);
|
||||
if (container.children.length > 5) container.lastElementChild.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MetaReasoningLayer {
|
||||
constructor(planner, blackboard) {
|
||||
this.planner = planner;
|
||||
this.blackboard = blackboard;
|
||||
this.reasoningCache = new Map();
|
||||
this.performanceMetrics = { totalReasoningTime: 0, calls: 0 };
|
||||
}
|
||||
|
||||
getCachedPlan(stateKey) {
|
||||
const cached = this.reasoningCache.get(stateKey);
|
||||
if (cached && (Date.now() - cached.timestamp < 10000)) return cached.plan;
|
||||
return null;
|
||||
}
|
||||
|
||||
cachePlan(stateKey, plan) {
|
||||
this.reasoningCache.set(stateKey, { plan, timestamp: Date.now() });
|
||||
}
|
||||
|
||||
reflect() {
|
||||
const avgTime = this.performanceMetrics.totalReasoningTime / (this.performanceMetrics.calls || 1);
|
||||
const container = document.getElementById('meta-log-content');
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<div class=\meta-stat\>CACHE SIZE: ${this.reasoningCache.size}</div>
|
||||
<div class=\meta-stat\>AVG LATENCY: ${avgTime.toFixed(2)}ms</div>
|
||||
<div class=\meta-stat\>STATUS: ${avgTime > 50 ? 'OPTIMIZING' : 'NOMINAL'}</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
track(startTime) {
|
||||
const duration = performance.now() - startTime;
|
||||
this.performanceMetrics.totalReasoningTime += duration;
|
||||
this.performanceMetrics.calls++;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user