thoughtmcp
Version:
AI that thinks more like humans do - MCP server with human-like cognitive architecture for enhanced reasoning, memory, and self-monitoring
373 lines • 13.9 kB
JavaScript
/**
* Semantic Memory System
*
* Implements embedding-based similarity search for semantic memories.
* Handles storage, retrieval, and relationship management of concepts.
*/
export class SemanticMemory {
concepts = new Map();
relations = new Map();
embeddingIndex = new Map();
activationIndex = new Map();
config;
initialized = false;
lastActivity = 0;
constructor(config) {
this.config = {
capacity: 50000,
embedding_dim: 768,
similarity_threshold: 0.7,
activation_decay: 0.05,
relation_strength_threshold: 0.3,
max_relations_per_concept: 20,
...config,
};
}
async initialize(config) {
if (config) {
this.config = { ...this.config, ...config };
}
this.concepts.clear();
this.relations.clear();
this.embeddingIndex.clear();
this.activationIndex.clear();
this.initialized = true;
this.lastActivity = Date.now();
}
async process(input) {
// Generic process method for CognitiveComponent interface
const inputObj = input;
if (typeof input === "object" && input !== null && inputObj?.concept) {
return this.store(inputObj.concept);
}
throw new Error("Invalid input for SemanticMemory.process()");
}
reset() {
this.concepts.clear();
this.relations.clear();
this.embeddingIndex.clear();
this.activationIndex.clear();
this.lastActivity = Date.now();
}
getStatus() {
return {
name: "SemanticMemory",
initialized: this.initialized,
active: this.concepts.size > 0,
last_activity: this.lastActivity,
};
}
/**
* Store a concept in semantic memory
*/
store(concept) {
this.lastActivity = Date.now();
// Generate ID if not provided
const conceptId = concept.id || this.generateConceptId(concept.content);
// Generate embedding if not provided
const embedding = concept.embedding || this.generateEmbedding(concept.content);
// Store the concept
const storedConcept = {
...concept,
id: conceptId,
embedding,
activation: concept.activation || 1.0,
last_accessed: Date.now(),
relations: concept.relations || [],
};
this.concepts.set(conceptId, storedConcept);
this.embeddingIndex.set(conceptId, embedding);
this.activationIndex.set(conceptId, storedConcept.activation);
// Apply capacity management after storing
if (this.concepts.size > this.config.capacity) {
this.pruneLeastActiveConcepts();
}
return conceptId;
}
/**
* Retrieve concepts based on similarity to cue
*/
retrieve(cue, threshold = this.config.similarity_threshold) {
this.lastActivity = Date.now();
const cueEmbedding = this.generateEmbedding(cue);
const matches = [];
// Compute similarity with all concepts
for (const [id, concept] of this.concepts) {
const embedding = this.embeddingIndex.get(id);
if (!embedding)
continue;
const similarity = this.computeCosineSimilarity(cueEmbedding, embedding);
if (similarity >= threshold) {
matches.push({ concept, similarity });
// Update activation for retrieved concepts
this.updateActivation(id, 0.1);
}
}
// Sort by similarity and return concepts
return matches
.sort((a, b) => b.similarity - a.similarity)
.map((match) => match.concept);
}
/**
* Add a relation between two concepts
*/
addRelation(from, to, type, strength) {
this.lastActivity = Date.now();
// Validate concepts exist
if (!this.concepts.has(from) || !this.concepts.has(to)) {
throw new Error(`Cannot create relation: concept(s) not found`);
}
// Check strength threshold
if (strength < this.config.relation_strength_threshold) {
return;
}
const relationId = `${from}-${type}-${to}`;
const relation = { from, to, type, strength };
this.relations.set(relationId, relation);
// Update concept relations
this.addRelationToConcept(from, relationId);
this.addRelationToConcept(to, relationId);
}
/**
* Get concepts related to a given concept
*/
getRelated(conceptId) {
const concept = this.concepts.get(conceptId);
if (!concept)
return [];
const relatedConcepts = [];
for (const relationId of concept.relations) {
const relation = this.relations.get(relationId);
if (!relation)
continue;
// Get the other concept in the relation
const otherConceptId = relation.from === conceptId ? relation.to : relation.from;
const otherConcept = this.concepts.get(otherConceptId);
if (otherConcept) {
relatedConcepts.push(otherConcept);
}
}
// Sort by activation level
return relatedConcepts.sort((a, b) => b.activation - a.activation);
}
/**
* Update activation level of a concept
*/
updateActivation(conceptId, delta) {
const concept = this.concepts.get(conceptId);
if (!concept)
return;
const newActivation = Math.max(0, Math.min(1, concept.activation + delta));
const updatedConcept = {
...concept,
activation: newActivation,
last_accessed: Date.now(),
};
this.concepts.set(conceptId, updatedConcept);
this.activationIndex.set(conceptId, newActivation);
}
/**
* Apply activation decay to all concepts
*/
applyDecay() {
const currentTime = Date.now();
for (const [id, concept] of this.concepts) {
const timeSinceAccess = (currentTime - concept.last_accessed) / (1000 * 60 * 60); // hours
const decayAmount = this.config.activation_decay * timeSinceAccess;
const newActivation = Math.max(0, concept.activation - decayAmount);
// Only update if there's actual decay
if (newActivation !== concept.activation) {
this.updateActivation(id, newActivation - concept.activation);
}
}
}
/**
* Find concepts by content similarity
*/
findSimilarConcepts(conceptId, maxResults = 10) {
const concept = this.concepts.get(conceptId);
if (!concept)
return [];
const embedding = this.embeddingIndex.get(conceptId);
if (!embedding)
return [];
const similarities = [];
for (const [id, otherConcept] of this.concepts) {
if (id === conceptId)
continue;
const otherEmbedding = this.embeddingIndex.get(id);
if (!otherEmbedding)
continue;
const similarity = this.computeCosineSimilarity(embedding, otherEmbedding);
similarities.push({ concept: otherConcept, similarity });
}
return similarities
.sort((a, b) => b.similarity - a.similarity)
.slice(0, maxResults)
.map((item) => item.concept);
}
/**
* Get concept by ID
*/
getConcept(conceptId) {
return this.concepts.get(conceptId);
}
/**
* Get all concepts with activation above threshold
*/
getActiveConcepts(threshold = 0.1) {
return Array.from(this.concepts.values())
.filter((concept) => concept.activation >= threshold)
.sort((a, b) => b.activation - a.activation);
}
/**
* Get relations of a specific type
*/
getRelationsByType(type) {
return Array.from(this.relations.values()).filter((relation) => relation.type === type);
}
// Private helper methods
generateConceptId(content) {
const contentStr = JSON.stringify(content);
let hash = 0;
for (let i = 0; i < contentStr.length; i++) {
const char = contentStr.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
return `concept_${Math.abs(hash).toString(16)}`;
}
generateEmbedding(content) {
// Simple embedding generation (in production, use a proper embedding model)
const contentStr = JSON.stringify(content).toLowerCase();
const embedding = new Array(this.config.embedding_dim).fill(0);
// Hash-based embedding generation
for (let i = 0; i < contentStr.length; i++) {
const char = contentStr.charCodeAt(i);
const index = char % this.config.embedding_dim;
embedding[index] += Math.sin(char * 0.1) * 0.1;
}
// Normalize the embedding
const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
if (magnitude > 0) {
for (let i = 0; i < embedding.length; i++) {
embedding[i] /= magnitude;
}
}
return embedding;
}
computeCosineSimilarity(a, b) {
if (a.length !== b.length)
return 0;
let dotProduct = 0;
let magnitudeA = 0;
let magnitudeB = 0;
for (let i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
magnitudeA += a[i] * a[i];
magnitudeB += b[i] * b[i];
}
magnitudeA = Math.sqrt(magnitudeA);
magnitudeB = Math.sqrt(magnitudeB);
if (magnitudeA === 0 || magnitudeB === 0)
return 0;
return dotProduct / (magnitudeA * magnitudeB);
}
addRelationToConcept(conceptId, relationId) {
const concept = this.concepts.get(conceptId);
if (!concept)
return;
// Check if we've reached the maximum relations limit
if (concept.relations.length >= this.config.max_relations_per_concept) {
// Remove the weakest relation
this.removeWeakestRelation(conceptId);
}
const updatedConcept = {
...concept,
relations: [...concept.relations, relationId],
};
this.concepts.set(conceptId, updatedConcept);
}
removeWeakestRelation(conceptId) {
const concept = this.concepts.get(conceptId);
if (!concept || concept.relations.length === 0)
return;
let weakestRelationId = "";
let weakestStrength = Infinity;
for (const relationId of concept.relations) {
const relation = this.relations.get(relationId);
if (relation && relation.strength < weakestStrength) {
weakestStrength = relation.strength;
weakestRelationId = relationId;
}
}
if (weakestRelationId) {
// Remove from concept
const updatedRelations = concept.relations.filter((id) => id !== weakestRelationId);
const updatedConcept = {
...concept,
relations: updatedRelations,
};
this.concepts.set(conceptId, updatedConcept);
// Remove the relation entirely
this.relations.delete(weakestRelationId);
}
}
pruneLeastActiveConcepts() {
// Remove concepts until we're at capacity
const conceptEntries = Array.from(this.concepts.entries());
const sortedByActivation = conceptEntries.sort((a, b) => a[1].activation - b[1].activation);
const toRemove = this.concepts.size - this.config.capacity;
for (let i = 0; i < toRemove && i < sortedByActivation.length; i++) {
const [conceptId] = sortedByActivation[i];
this.removeConcept(conceptId);
}
}
removeConcept(conceptId) {
const concept = this.concepts.get(conceptId);
if (!concept)
return;
// Remove all relations involving this concept
for (const relationId of concept.relations) {
this.relations.delete(relationId);
}
// Remove from all indexes
this.concepts.delete(conceptId);
this.embeddingIndex.delete(conceptId);
this.activationIndex.delete(conceptId);
// Remove references from other concepts
for (const [id, otherConcept] of this.concepts) {
const updatedRelations = otherConcept.relations.filter((relationId) => {
const relation = this.relations.get(relationId);
return (relation && relation.from !== conceptId && relation.to !== conceptId);
});
if (updatedRelations.length !== otherConcept.relations.length) {
this.concepts.set(id, {
...otherConcept,
relations: updatedRelations,
});
}
}
}
/**
* Simulate memory decay for testing purposes
*/
async simulateDecay(milliseconds) {
const decayFactor = Math.exp(-0.001 * (milliseconds / 1000)); // Small decay rate for semantic memory
for (const [id, concept] of this.concepts) {
// Apply decay to concept activation
const updatedConcept = {
...concept,
activation: concept.activation * decayFactor,
};
// Remove concepts that have decayed below threshold
if (updatedConcept.activation < 0.1) {
this.concepts.delete(id);
}
else {
this.concepts.set(id, updatedConcept);
}
}
}
}
//# sourceMappingURL=SemanticMemory.js.map