Skip to main content

GraphMemory

Struct GraphMemory 

Source
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

Source

pub fn get_db(&self) -> &DB

Get a reference to the underlying RocksDB instance (for backup/checkpoint).

Source

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).

Source

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

Source

pub fn get_entity(&self, uuid: &Uuid) -> Result<Option<EntityNode>>

Get entity by UUID

Source

pub fn delete_entity(&self, uuid: &Uuid) -> Result<bool>

Delete an entity and all its index entries.

Removes the entity from:

  1. entities CF (primary storage)
  2. entity_name_index (exact name → UUID)
  3. entity_lowercase_index (lowercase name → UUID)
  4. entity_stemmed_index (stemmed name → UUID)
  5. entity_embedding_cache (in-memory embedding vector)
  6. entity_pair_index CF (co-occurrence pair entries)
  7. Decrements entity_count

Returns true if the entity existed and was deleted.

Source

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:

  1. Exact match (O(1)) - fastest
  2. Case-insensitive match (O(1)) - common case
  3. Stemmed match (O(1)) - “running” matches “run”
  4. Substring match - “York” matches “New York City”
  5. Word-level match - “York” matches “New York”
Source

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.

Source

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).

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

pub fn get_relationship(&self, uuid: &Uuid) -> Result<Option<RelationshipEdge>>

Get relationship by UUID (raw, without decay applied)

Source

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.

Source

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.

Source

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:

  1. Removes the episode from the episodes DB
  2. Removes entity_episodes index entries
  3. Deletes relationship edges that were sourced from this episode
Source

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.

Source

pub fn add_episode(&self, episode: EpisodicNode) -> Result<Uuid>

Add an episodic node

Source

pub fn get_episode(&self, uuid: &Uuid) -> Result<Option<EpisodicNode>>

Get episode by UUID

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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).

Source

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.

Source

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:

  1. Look up EpisodicNode for episode_id → get entity_refs
  2. For each pair of entities, find their RelationshipEdge
  3. Call strengthen() on each edge (Hebbian boost + LTP detection + tier promotion)
  4. Batch write all updates
Source

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:

  1. Look up memory’s EpisodicNode (memory_id.0 == episode UUID)
  2. Get entity_refs from the episode
  3. For each entity, get relationships using get_entity_relationships
  4. Filter to edges where both endpoints are in the memory’s entity set
  5. Return average effective_strength of these intra-memory edges

Returns 0.5 (neutral) if no entities or relationships found.

Source

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).

Source

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.

Source

pub fn apply_decay(&self) -> Result<GraphDecayResult>

Apply decay to all synapses and prune weak edges (AUD-2)

Called during maintenance cycle to:

  1. Apply time-based decay to all edge strengths
  2. Remove edges that have decayed below threshold
  3. 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).

Source

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.

Source

pub fn get_stats(&self) -> Result<GraphStats>

Get graph statistics - O(1) using atomic counters

Source

pub fn get_all_entities(&self) -> Result<Vec<EntityNode>>

Get all entities in the graph

Source

pub fn get_all_relationships(&self) -> Result<Vec<RelationshipEdge>>

Get all relationships in the graph

Source

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§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts 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>

Converts 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)

Converts &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)

Converts &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
where T: Any + Send,

Source§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_sync(self: Box<T>) -> Box<dyn Any + Sync + Send>

Converts Box<Trait> (where Trait: DowncastSync) to Box<dyn Any + Send + Sync>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Converts Arc<Trait> (where Trait: DowncastSync) to Arc<Any>, which can then be downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> Fruit for T
where T: Send + Downcast,