pub struct GraphMemory { /* private fields */ }Expand description
Graph memory storage and operations
Uses a single RocksDB instance with 9 column families for all graph data. This reduces file descriptor usage from 9 separate DBs to 1 (sharing WAL, MANIFEST, LOCK).
Implementations§
Source§impl GraphMemory
impl GraphMemory
Sourcepub fn get_db(&self) -> &DB
pub fn get_db(&self) -> &DB
Get a reference to the underlying RocksDB instance (for backup/checkpoint).
Sourcepub fn new(path: &Path, shared_cache: Option<&Cache>) -> Result<Self>
pub fn new(path: &Path, shared_cache: Option<&Cache>) -> Result<Self>
Create a new graph memory system.
If shared_cache is provided, block-cache reads are charged against the
shared LRU cache (recommended for multi-tenant server mode). When None,
a small per-instance cache is created (standalone / test use).
Sourcepub fn add_entity(&self, entity: EntityNode) -> Result<Uuid>
pub fn add_entity(&self, entity: EntityNode) -> Result<Uuid>
Add or update an entity node Salience is updated using the formula: salience = base_salience * (1 + 0.1 * ln(mention_count)) This means frequently mentioned entities grow in salience (gravitational wells get heavier)
BUG-002 FIX: Handles crash recovery for orphaned entities/stale indices
Sourcepub fn get_entity(&self, uuid: &Uuid) -> Result<Option<EntityNode>>
pub fn get_entity(&self, uuid: &Uuid) -> Result<Option<EntityNode>>
Get entity by UUID
Sourcepub fn delete_entity(&self, uuid: &Uuid) -> Result<bool>
pub fn delete_entity(&self, uuid: &Uuid) -> Result<bool>
Delete an entity and all its index entries.
Removes the entity from:
entitiesCF (primary storage)entity_name_index(exact name → UUID)entity_lowercase_index(lowercase name → UUID)entity_stemmed_index(stemmed name → UUID)entity_embedding_cache(in-memory embedding vector)entity_pair_indexCF (co-occurrence pair entries)- Decrements
entity_count
Returns true if the entity existed and was deleted.
Sourcepub fn find_entity_by_name(&self, name: &str) -> Result<Option<EntityNode>>
pub fn find_entity_by_name(&self, name: &str) -> Result<Option<EntityNode>>
Find entity by name (case-insensitive, O(1) lookup)
Uses a multi-tier matching strategy:
- Exact match (O(1)) - fastest
- Case-insensitive match (O(1)) - common case
- Stemmed match (O(1)) - “running” matches “run”
- Substring match - “York” matches “New York City”
- Word-level match - “York” matches “New York”
Sourcepub fn find_entities_fuzzy(
&self,
name: &str,
max_results: usize,
) -> Result<Vec<EntityNode>>
pub fn find_entities_fuzzy( &self, name: &str, max_results: usize, ) -> Result<Vec<EntityNode>>
Find all entities matching a name with fuzzy matching
Returns multiple matches ranked by match quality. Useful for spreading activation across related entities.
Sourcepub fn find_relationship_between(
&self,
entity_a: &Uuid,
entity_b: &Uuid,
) -> Result<Option<RelationshipEdge>>
pub fn find_relationship_between( &self, entity_a: &Uuid, entity_b: &Uuid, ) -> Result<Option<RelationshipEdge>>
Find existing relationship between two entities (either direction)
O(1) lookup via entity-pair index, with fallback to linear scan for edges created before the pair index existed (migration path).
Sourcepub fn find_relationship_between_typed(
&self,
entity_a: &Uuid,
entity_b: &Uuid,
relation_type: &RelationType,
) -> Result<Option<RelationshipEdge>>
pub fn find_relationship_between_typed( &self, entity_a: &Uuid, entity_b: &Uuid, relation_type: &RelationType, ) -> Result<Option<RelationshipEdge>>
Find existing relationship between two entities with a specific relation type.
Unlike find_relationship_between which returns any edge between the pair,
this method only matches edges with the same RelationType. This allows
multiple semantically distinct edges (e.g. WorksWith + PartOf) between
the same entity pair.
Sourcepub fn add_relationship(&self, edge: RelationshipEdge) -> Result<Uuid>
pub fn add_relationship(&self, edge: RelationshipEdge) -> Result<Uuid>
Add a relationship edge (or strengthen existing one)
If an edge already exists between the two entities, strengthens it instead of creating a duplicate. This implements proper Hebbian learning: “neurons that fire together, wire together” - repeated co-occurrence strengthens the same synapse rather than creating parallel connections.
Sourcepub fn get_entity_relationships(
&self,
entity_uuid: &Uuid,
) -> Result<Vec<RelationshipEdge>>
pub fn get_entity_relationships( &self, entity_uuid: &Uuid, ) -> Result<Vec<RelationshipEdge>>
Get relationships for an entity with optional limit
Uses batch reading (multi_get) to eliminate N+1 query pattern. If limit is None, returns all edges (use sparingly for large graphs).
Sourcepub fn get_entity_relationships_limited(
&self,
entity_uuid: &Uuid,
limit: Option<usize>,
) -> Result<Vec<RelationshipEdge>>
pub fn get_entity_relationships_limited( &self, entity_uuid: &Uuid, limit: Option<usize>, ) -> Result<Vec<RelationshipEdge>>
Get relationships for an entity with limit, ordered by effective strength
Collects ALL edge UUIDs first, batch-reads them, sorts by effective_strength
descending, then returns the top limit strongest edges. This ensures
traversal and queries always use the most valuable connections.
When no limit is specified, returns all edges sorted by strength.
Sourcepub fn entity_edge_count(&self, entity_uuid: &Uuid) -> Result<usize>
pub fn entity_edge_count(&self, entity_uuid: &Uuid) -> Result<usize>
Calculate edge density for a specific entity (SHO-D5)
Returns the number of edges connected to this entity. Used for per-entity density calculation: dense entities use vector search, sparse entities use graph search.
This is an O(1) prefix count operation.
Sourcepub fn entities_average_density(
&self,
entity_uuids: &[Uuid],
) -> Result<Option<f32>>
pub fn entities_average_density( &self, entity_uuids: &[Uuid], ) -> Result<Option<f32>>
Calculate average edge density for a set of entities (SHO-D5)
Returns the mean number of edges per entity for the given UUIDs. Used to determine optimal retrieval strategy:
- Low density (<5 edges): Trust graph search (sparse, high-signal)
- High density (>20 edges): Trust vector search (dense, noisy)
Returns None if no entities provided.
Sourcepub fn entity_density_by_tier(
&self,
entity_uuid: &Uuid,
) -> Result<(usize, usize, usize, usize)>
pub fn entity_density_by_tier( &self, entity_uuid: &Uuid, ) -> Result<(usize, usize, usize, usize)>
Calculate edge density per tier for a specific entity (SHO-D5)
Returns counts of edges by tier: (L1_count, L2_count, L3_count, LTP_count) Useful for understanding if an entity’s graph is consolidated (mostly L3/LTP) or still noisy (mostly L1).
Sourcepub fn entity_consolidation_ratio(
&self,
entity_uuid: &Uuid,
) -> Result<Option<f32>>
pub fn entity_consolidation_ratio( &self, entity_uuid: &Uuid, ) -> Result<Option<f32>>
Calculate consolidated ratio for an entity (SHO-D5)
Returns the ratio of consolidated edges (L2 + L3 + LTP) to total edges. High ratio (>0.7) = trust graph search, Low ratio (<0.3) = trust vector search.
Returns None if entity has no edges.
Sourcepub fn get_relationship(&self, uuid: &Uuid) -> Result<Option<RelationshipEdge>>
pub fn get_relationship(&self, uuid: &Uuid) -> Result<Option<RelationshipEdge>>
Get relationship by UUID (raw, without decay applied)
Sourcepub fn get_relationship_with_effective_strength(
&self,
uuid: &Uuid,
) -> Result<Option<RelationshipEdge>>
pub fn get_relationship_with_effective_strength( &self, uuid: &Uuid, ) -> Result<Option<RelationshipEdge>>
Get relationship by UUID with effective strength (lazy decay calculation)
Returns the edge with strength reflecting time-based decay. This doesn’t persist the decay - just calculates what the strength would be. Use this for API responses to show accurate current strength.
Sourcepub fn delete_relationship(&self, uuid: &Uuid) -> Result<bool>
pub fn delete_relationship(&self, uuid: &Uuid) -> Result<bool>
Delete a relationship by UUID
Removes the relationship from storage and decrements the counter. Returns true if the relationship was found and deleted.
Sourcepub fn delete_episode(&self, episode_uuid: &Uuid) -> Result<bool>
pub fn delete_episode(&self, episode_uuid: &Uuid) -> Result<bool>
Delete an episode and clean up associated indices and orphan edges
When a memory is deleted, its corresponding episode should also be removed. This method:
- Removes the episode from the episodes DB
- Removes entity_episodes index entries
- Deletes relationship edges that were sourced from this episode
Sourcepub fn clear_all(&self) -> Result<(usize, usize, usize)>
pub fn clear_all(&self) -> Result<(usize, usize, usize)>
Clear all graph data (GDPR full erasure)
Wipes all entities, relationships, episodes, and all indices. Resets all counters to zero. Returns (entity_count, relationship_count, episode_count) that were cleared.
Sourcepub fn add_episode(&self, episode: EpisodicNode) -> Result<Uuid>
pub fn add_episode(&self, episode: EpisodicNode) -> Result<Uuid>
Add an episodic node
Sourcepub fn get_episode(&self, uuid: &Uuid) -> Result<Option<EpisodicNode>>
pub fn get_episode(&self, uuid: &Uuid) -> Result<Option<EpisodicNode>>
Get episode by UUID
Sourcepub fn get_episodes_by_entity(
&self,
entity_uuid: &Uuid,
) -> Result<Vec<EpisodicNode>>
pub fn get_episodes_by_entity( &self, entity_uuid: &Uuid, ) -> Result<Vec<EpisodicNode>>
Get all episodes that contain a specific entity
Uses inverted index for O(k) lookup instead of O(n) full scan. Collects episode UUIDs first, then batch-reads them using multi_get. Crucial for spreading activation algorithm.
Sourcepub fn traverse_from_entity(
&self,
start_uuid: &Uuid,
max_depth: usize,
) -> Result<GraphTraversal>
pub fn traverse_from_entity( &self, start_uuid: &Uuid, max_depth: usize, ) -> Result<GraphTraversal>
Traverse graph starting from an entity (breadth-first)
Implements Hebbian learning: edges traversed during retrieval are strengthened. This means frequently accessed pathways become stronger over time.
Returns TraversedEntity with hop distance and decay factor for proper scoring:
- hop 0 (start entity): decay = 1.0
- hop 1: decay = 0.7
- hop 2: decay = 0.49
- etc.
Performance: Uses batch edge reading and limits to handle large graphs.
Sourcepub fn traverse_from_entity_filtered(
&self,
start_uuid: &Uuid,
max_depth: usize,
min_strength: Option<f32>,
) -> Result<GraphTraversal>
pub fn traverse_from_entity_filtered( &self, start_uuid: &Uuid, max_depth: usize, min_strength: Option<f32>, ) -> Result<GraphTraversal>
BFS graph traversal with optional minimum edge strength filter.
When min_strength is Some, edges below the threshold are skipped
during traversal and NOT Hebbianly strengthened (prevents ghost edge revival).
Sourcepub fn traverse_weighted(
&self,
start_uuid: &Uuid,
max_depth: usize,
relation_types: Option<&[RelationType]>,
min_strength: f32,
) -> Result<GraphTraversal>
pub fn traverse_weighted( &self, start_uuid: &Uuid, max_depth: usize, relation_types: Option<&[RelationType]>, min_strength: f32, ) -> Result<GraphTraversal>
Weighted graph traversal with filtering (Dijkstra-style best-first)
Unlike BFS traverse_from_entity, this uses edge weights to prioritize stronger connections. Enables Cypher-like pattern matching:
- Filter by relationship types
- Filter by minimum edge strength
- Score paths by cumulative weight
Returns entities sorted by path score (strongest connections first).
Performance: Uses batch edge reading and early termination to handle large graphs (10000+ edges) efficiently.
Sourcepub fn traverse_bidirectional(
&self,
start_uuid: &Uuid,
goal_uuid: &Uuid,
max_depth: usize,
min_strength: f32,
) -> Result<GraphTraversal>
pub fn traverse_bidirectional( &self, start_uuid: &Uuid, goal_uuid: &Uuid, max_depth: usize, min_strength: f32, ) -> Result<GraphTraversal>
Bidirectional search between two entities (meet-in-middle)
Complexity: O(b^(d/2)) instead of O(b^d) for unidirectional search. Finds the shortest weighted path between start and goal. Returns entities along the path with their path scores.
Performance: Uses batch edge reading with limits.
Sourcepub fn match_pattern(
&self,
start_uuid: &Uuid,
pattern: &[(RelationType, bool)],
min_strength: f32,
) -> Result<Vec<Vec<TraversedEntity>>>
pub fn match_pattern( &self, start_uuid: &Uuid, pattern: &[(RelationType, bool)], min_strength: f32, ) -> Result<Vec<Vec<TraversedEntity>>>
Subgraph pattern matching (Cypher-like MATCH patterns)
Pattern format: Vec of (relation_type, direction) tuples Direction: true = outgoing (a->b), false = incoming (a<-b)
Example: MATCH (a)-[:WORKS_AT]->(b)-[:LOCATED_IN]->(c) Pattern: vec![(WorksAt, true), (LocatedIn, true)]
Returns all entities that match the pattern starting from start_uuid.
Sourcepub fn find_pattern_matches(
&self,
pattern: &[(RelationType, bool)],
min_strength: f32,
limit: usize,
) -> Result<Vec<Vec<TraversedEntity>>>
pub fn find_pattern_matches( &self, pattern: &[(RelationType, bool)], min_strength: f32, limit: usize, ) -> Result<Vec<Vec<TraversedEntity>>>
Find entities matching a pattern from any starting point
Scans all entities and finds those that match the given pattern. More expensive than match_pattern but doesn’t require a known start.
Pattern: Vec of (relation_type, is_outgoing) tuples Returns: All complete pattern matches with their paths.
Sourcepub fn invalidate_relationship(&self, edge_uuid: &Uuid) -> Result<()>
pub fn invalidate_relationship(&self, edge_uuid: &Uuid) -> Result<()>
Invalidate a relationship (temporal edge invalidation)
Guarded by synapse_update_lock to prevent race with strengthen/decay.
Sourcepub fn strengthen_synapse(&self, edge_uuid: &Uuid) -> Result<()>
pub fn strengthen_synapse(&self, edge_uuid: &Uuid) -> Result<()>
Strengthen a synapse (Hebbian learning)
Called when an edge is traversed during memory retrieval. Implements “neurons that fire together, wire together”.
Uses a mutex to prevent race conditions during concurrent updates (SHO-64).
Sourcepub fn batch_strengthen_synapses(&self, edge_uuids: &[Uuid]) -> Result<usize>
pub fn batch_strengthen_synapses(&self, edge_uuids: &[Uuid]) -> Result<usize>
Batch strengthen multiple synapses atomically (SHO-65)
More efficient than calling strengthen_synapse individually for each edge. Uses RocksDB WriteBatch for atomic multi-write and a single lock acquisition.
Returns the number of synapses successfully strengthened.
Sourcepub fn record_memory_coactivation(&self, memory_ids: &[Uuid]) -> Result<usize>
pub fn record_memory_coactivation(&self, memory_ids: &[Uuid]) -> Result<usize>
Record co-retrieval of memories (Hebbian learning between memories)
When memories are retrieved together, they form associations. This creates or strengthens CoRetrieved edges between all pairs of memories.
Note: Limits to top N memories to avoid O(n²) explosion on large retrievals. Returns the number of edges created/strengthened.
Sourcepub fn strengthen_memory_edges(
&self,
edge_boosts: &[(String, String, f32)],
) -> Result<(usize, Vec<EdgePromotionBoost>)>
pub fn strengthen_memory_edges( &self, edge_boosts: &[(String, String, f32)], ) -> Result<(usize, Vec<EdgePromotionBoost>)>
Batch strengthen edges between memory pairs from replay consolidation
Takes edge boosts from memory replay and applies Hebbian strengthening. Creates edges if they don’t exist, strengthens if they do.
Returns (count_strengthened, promotion_boosts) where promotion_boosts contains signals for any edge tier promotions that occurred (Direction 1 coupling).
Sourcepub fn find_memory_associations(
&self,
memory_id: &Uuid,
max_results: usize,
) -> Result<Vec<(Uuid, f32)>>
pub fn find_memory_associations( &self, memory_id: &Uuid, max_results: usize, ) -> Result<Vec<(Uuid, f32)>>
Find memories associated with a given memory through co-retrieval
Uses weighted graph traversal prioritizing stronger associations. Returns memory UUIDs sorted by association strength.
Sourcepub fn strengthen_episode_entity_edges(
&self,
episode_id: &Uuid,
) -> Result<usize>
pub fn strengthen_episode_entity_edges( &self, episode_id: &Uuid, ) -> Result<usize>
Strengthen entity-entity edges for a replayed memory’s episode.
During consolidation replay, this reinforces the entity relationships that were involved in the replayed memory. This is “Direction 3” of the Hebbian maintenance system — entity-entity edges get strengthened alongside memory-to-memory edges (Direction 1) and lazy pruning (Direction 2).
Algorithm:
- Look up EpisodicNode for episode_id → get entity_refs
- For each pair of entities, find their RelationshipEdge
- Call strengthen() on each edge (Hebbian boost + LTP detection + tier promotion)
- Batch write all updates
Sourcepub fn get_memory_hebbian_strength(&self, memory_id: &MemoryId) -> Option<f32>
pub fn get_memory_hebbian_strength(&self, memory_id: &MemoryId) -> Option<f32>
Get average Hebbian strength for a memory based on its entity relationships
This looks up the entities referenced by the memory and averages their relationship strengths in the graph. Used for composite relevance scoring.
The algorithm:
- Look up memory’s EpisodicNode (memory_id.0 == episode UUID)
- Get entity_refs from the episode
- For each entity, get relationships using get_entity_relationships
- Filter to edges where both endpoints are in the memory’s entity set
- Return average effective_strength of these intra-memory edges
Returns 0.5 (neutral) if no entities or relationships found.
Sourcepub fn decay_synapse(&self, edge_uuid: &Uuid) -> Result<bool>
pub fn decay_synapse(&self, edge_uuid: &Uuid) -> Result<bool>
Apply decay to a synapse
Returns true if the synapse should be pruned (non-potentiated and below threshold)
Uses a mutex to prevent race conditions during concurrent updates (SHO-64).
Sourcepub fn batch_decay_synapses(&self, edge_uuids: &[Uuid]) -> Result<Vec<Uuid>>
pub fn batch_decay_synapses(&self, edge_uuids: &[Uuid]) -> Result<Vec<Uuid>>
Batch decay multiple synapses atomically
Returns a vector of edge UUIDs that should be pruned.
Sourcepub fn apply_decay(&self) -> Result<GraphDecayResult>
pub fn apply_decay(&self) -> Result<GraphDecayResult>
Apply decay to all synapses and prune weak edges (AUD-2)
Called during maintenance cycle to:
- Apply time-based decay to all edge strengths
- Remove edges that have decayed below threshold
- Detect orphaned entities (entities that lost all their edges)
Returns a GraphDecayResult with pruned count and orphaned entity/memory IDs
for Direction 2 coupling (edge pruning → orphan detection).
Sourcepub fn flush_pending_maintenance(&self) -> Result<GraphDecayResult>
pub fn flush_pending_maintenance(&self) -> Result<GraphDecayResult>
Flush pending maintenance from opportunistic pruning queues.
Called every maintenance cycle (5 min). Instead of scanning all 34k+ edges,
this only processes edges that were found below prune threshold during normal
reads (via get_entity_relationships_limited). Typical cost: 0-50 targeted
deletes per cycle vs a full CF iterator scan.
Sourcepub fn get_stats(&self) -> Result<GraphStats>
pub fn get_stats(&self) -> Result<GraphStats>
Get graph statistics - O(1) using atomic counters
Sourcepub fn get_all_entities(&self) -> Result<Vec<EntityNode>>
pub fn get_all_entities(&self) -> Result<Vec<EntityNode>>
Get all entities in the graph
Sourcepub fn get_all_relationships(&self) -> Result<Vec<RelationshipEdge>>
pub fn get_all_relationships(&self) -> Result<Vec<RelationshipEdge>>
Get all relationships in the graph
Sourcepub fn get_universe(&self) -> Result<MemoryUniverse>
pub fn get_universe(&self) -> Result<MemoryUniverse>
Get the Memory Universe visualization data Returns entities as “stars” with positions based on their relationships, sized by salience, and colored by entity type.
Auto Trait Implementations§
impl !Freeze for GraphMemory
impl !RefUnwindSafe for GraphMemory
impl Send for GraphMemory
impl Sync for GraphMemory
impl Unpin for GraphMemory
impl UnsafeUnpin for GraphMemory
impl !UnwindSafe for GraphMemory
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more