Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
A comprehensive collection of Agent Skills for context engineering, multi-agent architectures, and production agent systems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
skills/memory-systems/scripts/memory_store.py
1"""Memory System Implementation.23Provides composable building blocks for agent memory: vector stores with4metadata indexing, property graphs for entity relationships, and temporal5knowledge graphs for facts that change over time.67Use when:8- Building a memory persistence layer for an agent that must retain9knowledge across sessions.10- Prototyping memory architectures before committing to a production11framework (Mem0, Zep/Graphiti, Letta, Cognee).12- Combining semantic search with graph-based entity retrieval in a13single integrated system.1415Typical usage::1617from memory_store import IntegratedMemorySystem18mem = IntegratedMemorySystem()19mem.start_session("session-001")20mem.store_fact("Alice prefers dark mode", entity="Alice")21results = mem.retrieve_memories("theme preference")22"""2324import hashlib25import json26from datetime import datetime27from typing import Any, Dict, List, Optional2829import numpy as np3031__all__ = [32"VectorStore",33"PropertyGraph",34"TemporalKnowledgeGraph",35"IntegratedMemorySystem",36]373839class VectorStore:40"""Simple vector store with metadata indexing.4142Use when: the agent needs semantic similarity search over stored facts43with optional entity and temporal filtering.44"""4546def __init__(self, dimension: int = 768) -> None:47self.dimension: int = dimension48self.vectors: List[np.ndarray] = []49self.metadata: List[Dict[str, Any]] = []50self.entity_index: Dict[str, List[int]] = {}51self.time_index: Dict[str, List[int]] = {}5253def add(self, text: str, metadata: Optional[Dict[str, Any]] = None) -> int:54"""Add document to store.5556Use when: persisting a new fact or observation that the agent should57be able to retrieve later via semantic search.58"""59metadata = metadata or {}60embedding: np.ndarray = self._embed(text)61index: int = len(self.vectors)6263self.vectors.append(embedding)64self.metadata.append(metadata)6566# Index by entity67if "entity" in metadata:68entity: str = metadata["entity"]69if entity not in self.entity_index:70self.entity_index[entity] = []71self.entity_index[entity].append(index)7273# Index by time74if "valid_from" in metadata:75time_key: str = self._time_key(metadata["valid_from"])76if time_key not in self.time_index:77self.time_index[time_key] = []78self.time_index[time_key].append(index)7980return index8182def search(83self,84query: str,85limit: int = 5,86filters: Optional[Dict[str, Any]] = None,87) -> List[Dict[str, Any]]:88"""Search for similar documents.8990Use when: retrieving memories relevant to a query, optionally91narrowed by metadata filters (entity, session, time range).92"""93query_embedding: np.ndarray = self._embed(query)9495scores: List[tuple[int, float]] = []96for i, vec in enumerate(self.vectors):97score: float = float(98np.dot(query_embedding, vec)99/ (np.linalg.norm(query_embedding) * np.linalg.norm(vec) + 1e-8)100)101102# Apply filters103if filters and not self._matches_filters(self.metadata[i], filters):104score = -1.0105106scores.append((i, score))107108scores.sort(key=lambda x: x[1], reverse=True)109110results: List[Dict[str, Any]] = []111for idx, score in scores[:limit]:112if score > 0:113results.append(114{115"index": idx,116"score": score,117"text": self.metadata[idx].get("text", ""),118"metadata": self.metadata[idx],119}120)121122return results123124def search_by_entity(125self, entity: str, query: str = "", limit: int = 5126) -> List[Dict[str, Any]]:127"""Search within specific entity.128129Use when: the agent needs all memories associated with a known130entity, optionally ranked by relevance to a query.131"""132indices: List[int] = self.entity_index.get(entity, [])133134if not indices:135return []136137if query:138query_embedding: np.ndarray = self._embed(query)139scored: List[tuple[int, float, Dict[str, Any]]] = []140for i in indices:141vec: np.ndarray = self.vectors[i]142score: float = float(143np.dot(query_embedding, vec)144/ (np.linalg.norm(query_embedding) * np.linalg.norm(vec) + 1e-8)145)146scored.append((i, score, self.metadata[i]))147148scored.sort(key=lambda x: x[1], reverse=True)149return [150{"index": i, "score": s, "metadata": m}151for i, s, m in scored[:limit]152]153else:154return [155{"index": i, "score": 1.0, "metadata": self.metadata[i]}156for i in indices[:limit]157]158159def _embed(self, text: str) -> np.ndarray:160"""Generate embedding for text.161162In production, replace with an actual embedding model. This163deterministic stub uses the text hash as a random seed so that164identical texts always produce identical vectors. Uses a local165RNG to avoid corrupting global numpy random state.166"""167rng = np.random.default_rng(hash(text) % (2**32))168return rng.standard_normal(self.dimension)169170def _time_key(self, timestamp: Any) -> str:171"""Create time key for indexing."""172if isinstance(timestamp, datetime):173return timestamp.strftime("%Y-%m")174return str(timestamp)175176def _matches_filters(self, metadata: Dict[str, Any], filters: Dict[str, Any]) -> bool:177"""Check if metadata matches filters."""178for key, value in filters.items():179if key not in metadata:180return False181if isinstance(value, list):182if metadata[key] not in value:183return False184elif metadata[key] != value:185return False186return True187188189class PropertyGraph:190"""Simple property graph storage.191192Use when: the agent needs to maintain entity relationships and193traverse connections between nodes (e.g., "find all projects194associated with this user").195"""196197def __init__(self) -> None:198self.nodes: Dict[str, Dict[str, Any]] = {}199self.edges: Dict[str, Dict[str, Any]] = {}200self.entity_registry: Dict[str, str] = {} # name -> node_id201self.node_index: Dict[str, List[str]] = {} # label -> node_ids202self.edge_index: Dict[str, List[str]] = {} # type -> edge_ids203204def get_or_create_node(205self, name: str, label: str = "Entity", properties: Optional[Dict[str, Any]] = None206) -> str:207"""Get existing node by name, or create a new one.208209Use when: storing an entity that may already exist. The entity210registry ensures identity is maintained across interactions211("John Doe" always maps to the same node).212"""213if name in self.entity_registry:214node_id: str = self.entity_registry[name]215if properties:216self.nodes[node_id]["properties"].update(properties)217return node_id218node_id = self.create_node(label, {**(properties or {}), "name": name})219self.entity_registry[name] = node_id220return node_id221222def create_node(self, label: str, properties: Optional[Dict[str, Any]] = None) -> str:223"""Create node with label and properties.224225Use when: adding a new entity to the graph that does not need226identity deduplication (prefer get_or_create_node otherwise).227"""228node_id: str = hashlib.md5(f"{label}{datetime.now().isoformat()}".encode()).hexdigest()[:16]229230self.nodes[node_id] = {231"id": node_id,232"label": label,233"properties": properties or {},234"created_at": datetime.now().isoformat(),235}236237if label not in self.node_index:238self.node_index[label] = []239self.node_index[label].append(node_id)240241return node_id242243def create_relationship(244self,245source_id: str,246rel_type: str,247target_id: str,248properties: Optional[Dict[str, Any]] = None,249) -> str:250"""Create directed relationship between nodes.251252Use when: recording a connection between two entities (e.g.,253WORKS_AT, LIVES_IN, DEPENDS_ON).254"""255if source_id not in self.nodes:256raise ValueError(f"Unknown source node: {source_id}")257if target_id not in self.nodes:258raise ValueError(f"Unknown target node: {target_id}")259260edge_id: str = hashlib.md5(261f"{source_id}{rel_type}{target_id}{datetime.now().isoformat()}".encode()262).hexdigest()[:16]263264self.edges[edge_id] = {265"id": edge_id,266"source": source_id,267"target": target_id,268"type": rel_type,269"properties": properties or {},270"created_at": datetime.now().isoformat(),271}272273if rel_type not in self.edge_index:274self.edge_index[rel_type] = []275self.edge_index[rel_type].append(edge_id)276277return edge_id278279def query(self, pattern: Dict[str, Any]) -> List[Dict[str, Any]]:280"""Query graph with simple pattern matching.281282Use when: finding relationships that match a structural pattern283(e.g., all WORKS_AT edges from Person nodes).284"""285results: List[Dict[str, Any]] = []286287# Match by edge type288if "type" in pattern:289edge_ids: List[str] = self.edge_index.get(pattern["type"], [])290for eid in edge_ids:291edge: Dict[str, Any] = self.edges[eid]292source: Dict[str, Any] = self.nodes.get(edge["source"], {})293target: Dict[str, Any] = self.nodes.get(edge["target"], {})294295# Match source label296if "source_label" in pattern:297if source.get("label") != pattern["source_label"]:298continue299300# Match target label301if "target_label" in pattern:302if target.get("label") != pattern["target_label"]:303continue304305results.append({"source": source, "edge": edge, "target": target})306307return results308309def get_node(self, node_id: str) -> Optional[Dict[str, Any]]:310"""Get node by ID."""311return self.nodes.get(node_id)312313def get_relationships(314self, node_id: str, direction: str = "both"315) -> List[Dict[str, Any]]:316"""Get relationships for a node.317318Use when: retrieving all connections for a given entity to build319a complete entity context.320"""321relationships: List[Dict[str, Any]] = []322323for edge in self.edges.values():324if direction in ["outgoing", "both"] and edge["source"] == node_id:325relationships.append(326{327"edge": edge,328"target": self.nodes.get(edge["target"]),329"direction": "outgoing",330}331)332if direction in ["incoming", "both"] and edge["target"] == node_id:333relationships.append(334{335"edge": edge,336"source": self.nodes.get(edge["source"]),337"direction": "incoming",338}339)340341return relationships342343344class TemporalKnowledgeGraph(PropertyGraph):345"""Property graph with temporal validity for facts.346347Use when: the agent must track facts that change over time and348answer time-scoped queries (e.g., "where did the user live in349March 2024?").350"""351352def create_temporal_relationship(353self,354source_id: str,355rel_type: str,356target_id: str,357valid_from: datetime,358valid_until: Optional[datetime] = None,359properties: Optional[Dict[str, Any]] = None,360) -> str:361"""Create relationship with temporal validity.362363Use when: recording a fact that has a known start time and364may expire (e.g., employment, address, subscription status).365"""366edge_id: str = super().create_relationship(367source_id, rel_type, target_id, properties368)369370# Add temporal properties371self.edges[edge_id]["valid_from"] = valid_from.isoformat()372self.edges[edge_id]["valid_until"] = (373valid_until.isoformat() if valid_until else None374)375376return edge_id377378def query_at_time(379self, query: Dict[str, Any], query_time: datetime380) -> List[Dict[str, Any]]:381"""Query graph state at specific time.382383Use when: answering point-in-time questions about entities384(e.g., "what was true on date X?").385"""386results: List[Dict[str, Any]] = []387388# Get base query results389base_results: List[Dict[str, Any]] = self.query(query)390391for result in base_results:392edge: Dict[str, Any] = result["edge"]393valid_from: datetime = datetime.fromisoformat(394edge.get("valid_from", "1970-01-01")395)396valid_until: Optional[str] = edge.get("valid_until")397398# Check temporal validity399if valid_from <= query_time:400if valid_until is None or datetime.fromisoformat(valid_until) > query_time:401results.append(402{403**result,404"valid_from": valid_from,405"valid_until": valid_until,406}407)408409return results410411def query_time_range(412self,413query: Dict[str, Any],414start_time: datetime,415end_time: datetime,416) -> List[Dict[str, Any]]:417"""Query facts valid during time range.418419Use when: retrieving all facts that overlap with a given time420window (e.g., "what changed between January and June?").421"""422results: List[Dict[str, Any]] = []423424base_results: List[Dict[str, Any]] = self.query(query)425426for result in base_results:427edge: Dict[str, Any] = result["edge"]428valid_from: datetime = datetime.fromisoformat(429edge.get("valid_from", "1970-01-01")430)431valid_until: Optional[str] = edge.get("valid_until")432433# Check if overlaps with query range434until_dt: datetime = (435datetime.fromisoformat(valid_until) if valid_until else datetime.max436)437438if until_dt >= start_time and valid_from <= end_time:439results.append(440{441**result,442"valid_from": valid_from,443"valid_until": valid_until,444}445)446447return results448449450# ---------------------------------------------------------------------------451# Memory System Integration452# ---------------------------------------------------------------------------453454455class IntegratedMemorySystem:456"""Integrated memory system combining vector store and graph.457458Use when: the agent needs both semantic search over facts and459graph-based entity relationship traversal in a single unified460interface. This class composes VectorStore and TemporalKnowledgeGraph,461enriching vector search results with graph context.462"""463464def __init__(self) -> None:465self.vector_store: VectorStore = VectorStore()466self.graph: TemporalKnowledgeGraph = TemporalKnowledgeGraph()467self.session_id: str = ""468469def start_session(self, session_id: str) -> None:470"""Start a new memory session.471472Use when: beginning a new conversation or task that should473scope its memories to a distinct session identifier.474"""475self.session_id = session_id476477def store_fact(478self,479fact: str,480entity: str,481timestamp: Optional[datetime] = None,482relationships: Optional[List[Dict[str, Any]]] = None,483) -> None:484"""Store a fact with entity and relationships.485486Use when: the agent observes a new piece of information that487should be persisted for future retrieval. Stores in both the488vector store (for semantic search) and the graph (for entity489traversal).490"""491# Store in vector store492self.vector_store.add(493fact,494{495"text": fact,496"entity": entity,497"valid_from": (timestamp or datetime.now()).isoformat(),498"session_id": self.session_id,499},500)501502# Get or create entity node (uses registry for identity)503entity_node_id: str = self.graph.get_or_create_node(entity)504505# Create relationships506if relationships:507for rel in relationships:508target_node_id: str = self.graph.get_or_create_node(rel["target"])509self.graph.create_relationship(510entity_node_id,511rel["type"],512target_node_id,513properties=rel.get("properties", {}),514)515516def retrieve_memories(517self,518query: str,519entity_filter: Optional[str] = None,520time_filter: Optional[Dict[str, Any]] = None,521limit: int = 5,522) -> List[Dict[str, Any]]:523"""Retrieve memories matching query.524525Use when: the agent needs to recall previously stored facts,526optionally filtered by entity or time. Results are enriched527with graph relationships for each matched entity.528"""529# Vector search530filters: Dict[str, Any] = {"session_id": self.session_id}531if entity_filter:532filters["entity"] = entity_filter533534results: List[Dict[str, Any]] = self.vector_store.search(535query, limit=limit, filters=filters536)537538# Enrich with graph relationships539for result in results:540entity: Optional[str] = result["metadata"].get("entity")541if entity:542node_id: Optional[str] = self.graph.entity_registry.get(entity)543if node_id:544result["relationships"] = self.graph.get_relationships(node_id)545546return results547548def retrieve_entity_context(self, entity: str) -> Dict[str, Any]:549"""Retrieve complete context for an entity.550551Use when: the agent needs a full picture of a single entity552including its properties, all relationships, and associated553vector memories.554"""555node_id: Optional[str] = self.graph.entity_registry.get(entity)556557# Get entity node558entity_node: Optional[Dict[str, Any]] = (559self.graph.get_node(node_id) if node_id else None560)561562# Get relationships563relationships: List[Dict[str, Any]] = (564self.graph.get_relationships(node_id) if node_id else []565)566567# Get vector memories568memories: List[Dict[str, Any]] = self.vector_store.search_by_entity(569entity, limit=10570)571572return {573"entity": entity_node,574"relationships": relationships,575"memories": memories,576}577578def consolidate(self) -> None:579"""Consolidate memories and remove outdated information.580581Use when: memory count exceeds a threshold, retrieval quality582degrades, or on a scheduled interval. In production, implement:583- Merge related facts into summaries584- Update validity periods on stale entries585- Archive obsolete facts (invalidate, do not discard)586"""587pass588589590if __name__ == "__main__":591# Quick smoke test demonstrating the integrated memory system.592mem = IntegratedMemorySystem()593mem.start_session("demo-session")594595# Store facts with entity relationships596mem.store_fact(597"Alice prefers dark mode",598entity="Alice",599relationships=[{"target": "dark mode", "type": "PREFERS"}],600)601mem.store_fact(602"Alice works at Acme Corp",603entity="Alice",604relationships=[{"target": "Acme Corp", "type": "WORKS_AT"}],605)606607# Semantic retrieval608results = mem.retrieve_memories("theme preference")609print(f"Search results: {len(results)} memories found")610for r in results:611print(f" score={r['score']:.3f} text={r['text']}")612613# Entity context614context = mem.retrieve_entity_context("Alice")615print(f"\nAlice context: {len(context['relationships'])} relationships, "616f"{len(context['memories'])} memories")617