pub struct InMemoryGraphMemory<N, E>{ /* private fields */ }Expand description
In-process GraphMemory backed by BTreeMap adjacency lists,
sharded per namespace.
Locking model:
- The outer
DashMapkeys per-namespace tables. DashMap shards the map across N internal stripes so insertions and lookups across distinct namespaces never serialise on a single mutex. - Each table sits behind its own
RwLock. Writes to one namespace block writes to that namespace only; concurrent writes against distinct namespaces run in parallel. - Reads against a namespace share the read side of the per-table
RwLock, so dashboards / agents querying the same graph in parallel scale linearly until contention on a single tenant’s write rate.
Cheap to clone — internal state is Arc<DashMap<...>>-shared,
so every clone observes the same graph.
Implementations§
Source§impl<N, E> InMemoryGraphMemory<N, E>
impl<N, E> InMemoryGraphMemory<N, E>
Sourcepub fn total_nodes(&self) -> usize
pub fn total_nodes(&self) -> usize
Total node count across all namespaces — useful for tests. Iterates DashMap entries (each acquired via its own shard lock) and reads each per-namespace lock independently.
Sourcepub fn total_edges(&self) -> usize
pub fn total_edges(&self) -> usize
Total edge count across all namespaces — useful for tests.
Trait Implementations§
Source§impl<N, E> Clone for InMemoryGraphMemory<N, E>
impl<N, E> Clone for InMemoryGraphMemory<N, E>
Source§impl<N, E> Default for InMemoryGraphMemory<N, E>
impl<N, E> Default for InMemoryGraphMemory<N, E>
Source§impl<N, E> GraphMemory<N, E> for InMemoryGraphMemory<N, E>
impl<N, E> GraphMemory<N, E> for InMemoryGraphMemory<N, E>
Source§fn add_node<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
node: N,
) -> Pin<Box<dyn Future<Output = Result<NodeId>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn add_node<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
node: N,
) -> Pin<Box<dyn Future<Output = Result<NodeId>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Insert
node and return its assigned id.Source§fn add_edge<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
from: &'life3 NodeId,
to: &'life4 NodeId,
edge: E,
timestamp: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = Result<EdgeId>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
fn add_edge<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
from: &'life3 NodeId,
to: &'life4 NodeId,
edge: E,
timestamp: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = Result<EdgeId>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Insert an edge from
from to to carrying edge.
timestamp is supplied by the caller so re-inserting after
a replay produces deterministic edges.Source§fn add_edges_batch<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
edges: Vec<(NodeId, NodeId, E, DateTime<Utc>)>,
) -> Pin<Box<dyn Future<Output = Result<Vec<EdgeId>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn add_edges_batch<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
edges: Vec<(NodeId, NodeId, E, DateTime<Utc>)>,
) -> Pin<Box<dyn Future<Output = Result<Vec<EdgeId>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Insert a batch of edges atomically. Each tuple is
(from, to, edge, timestamp); endpoints must already exist
(same contract as Self::add_edge). Returns the assigned
EdgeIds in input order. Read moreSource§fn get_node<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
id: &'life3 NodeId,
) -> Pin<Box<dyn Future<Output = Result<Option<N>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn get_node<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
id: &'life3 NodeId,
) -> Pin<Box<dyn Future<Output = Result<Option<N>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Look up a node by id (verb-family
get per
.claude/rules/naming.md — single-item primary-key
lookup).Source§fn get_edge<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
edge_id: &'life3 EdgeId,
) -> Pin<Box<dyn Future<Output = Result<Option<GraphHop<E>>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn get_edge<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
edge_id: &'life3 EdgeId,
) -> Pin<Box<dyn Future<Output = Result<Option<GraphHop<E>>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Look up an edge by id and return the full structural body
(
GraphHop<E> — from, to, edge, timestamp).
Operators rarely want the payload alone for edges; the
endpoints and timestamp are usually load-bearing for any
follow-up decision (audit context, freshness check,
neighbour navigation). Returning the full hop saves a
second lookup. Read moreSource§fn neighbors<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
node: &'life3 NodeId,
direction: Direction,
) -> Pin<Box<dyn Future<Output = Result<Vec<(EdgeId, NodeId, E)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn neighbors<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
node: &'life3 NodeId,
direction: Direction,
) -> Pin<Box<dyn Future<Output = Result<Vec<(EdgeId, NodeId, E)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Edges incident to
node in the requested direction. Each
triple is (EdgeId, neighbour NodeId, edge payload).Source§fn traverse<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
start: &'life3 NodeId,
direction: Direction,
max_depth: usize,
) -> Pin<Box<dyn Future<Output = Result<Vec<GraphHop<E>>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn traverse<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
start: &'life3 NodeId,
direction: Direction,
max_depth: usize,
) -> Pin<Box<dyn Future<Output = Result<Vec<GraphHop<E>>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Breadth-first traversal starting at
start, expanding up to
max_depth hops along edges in the requested direction.
Returns the visited hops in BFS order (excluding the seed
node, which has no inbound edge in this traversal). Use
Direction::Both for relationship-graph queries that
don’t care about edge polarity (knowledge graphs typically
want this).Source§fn find_path<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
from: &'life3 NodeId,
to: &'life4 NodeId,
direction: Direction,
max_depth: usize,
) -> Pin<Box<dyn Future<Output = Result<Option<Vec<GraphHop<E>>>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
fn find_path<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
from: &'life3 NodeId,
to: &'life4 NodeId,
direction: Direction,
max_depth: usize,
) -> Pin<Box<dyn Future<Output = Result<Option<Vec<GraphHop<E>>>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Shortest unweighted path from
from to to (BFS) along
edges in the requested direction. Returns the sequence of
hops; Some(vec![]) means from == to (already at
destination — no edges traversed); None means no path
exists within max_depth hops.Source§fn temporal_filter<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
from: DateTime<Utc>,
to: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = Result<Vec<GraphHop<E>>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn temporal_filter<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
from: DateTime<Utc>,
to: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = Result<Vec<GraphHop<E>>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Edges whose timestamp falls in
[from, to). Useful for
audit-log style queries (“what relationships did the agent
learn last week”).Source§fn node_count<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn node_count<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Count nodes in
ns. Cheap operator metric for
size-based decisions (paginate vs stream, fast-fail
empty-namespace check, audit / dashboard surface).
Default impl returns 0.Source§fn edge_count<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn edge_count<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Count edges in
ns. Cheap operator metric — same
rationale as Self::node_count. Default impl returns
0.Source§fn delete_edge<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
edge_id: &'life3 EdgeId,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn delete_edge<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
edge_id: &'life3 EdgeId,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Drop one edge by id. Idempotent — deleting an absent
edge succeeds. Required — backends that don’t support
edge deletion are degenerate; closed the
CRUD-completeness gap.
Source§fn delete_node<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
node_id: &'life3 NodeId,
) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn delete_node<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
node_id: &'life3 NodeId,
) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Drop one node by id and every edge incident to it.
Cascades — operators that don’t want cascading delete
every incident edge first via
Self::delete_edge and
then call this. Returns the count of removed edges so
callers can log or expose cleanup metrics; 0 when the
node had no edges (or was absent — the operation is
idempotent). Read moreSource§fn prune_older_than<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
ttl: Duration,
) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn prune_older_than<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_ctx: &'life1 ExecutionContext,
ns: &'life2 Namespace,
ttl: Duration,
) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Drop every edge in
ns whose timestamp is older than
ttl ago. Returns the count of removed edges so callers
can log or expose pruning metrics. Read moreAuto Trait Implementations§
impl<N, E> Freeze for InMemoryGraphMemory<N, E>
impl<N, E> !RefUnwindSafe for InMemoryGraphMemory<N, E>
impl<N, E> Send for InMemoryGraphMemory<N, E>
impl<N, E> Sync for InMemoryGraphMemory<N, E>
impl<N, E> Unpin for InMemoryGraphMemory<N, E>
impl<N, E> UnsafeUnpin for InMemoryGraphMemory<N, E>
impl<N, E> !UnwindSafe for InMemoryGraphMemory<N, E>
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
Mutably borrows from an owned value. Read more