pub trait GraphMemory<N, E>:
Send
+ Sync
+ 'static{
Show 14 methods
// Required methods
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_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 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 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 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 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 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 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_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;
// Provided methods
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 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 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 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 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 { ... }
}Expand description
Generic graph-of-knowledge memory. Trait so backends (Neo4j,
ArangoDB, Postgres-with-recursive-CTE) can plug in without
touching the consumer code; reference in-process impl is
InMemoryGraphMemory.
Required Methods§
Sourcefn 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.
Sourcefn 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.
Sourcefn 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).
Sourcefn 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).
Sourcefn 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).
Sourcefn 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.
Sourcefn 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”).
Sourcefn 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.
Sourcefn 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).
Cascade is the right default because the alternative
(leaving dangling edges that point at a deleted node)
would break the invariant “every edge endpoint is a
resolvable node id” that traversal relies on. Refusing
when edges exist (the SQL RESTRICT shape) would force
every operator into a manual edge-delete loop.
Provided Methods§
Sourcefn 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.
Backends with native bulk-insert support (e.g.
PgGraphMemory’s INSERT … SELECT FROM UNNEST(…)) override
this to fold N round-trips into one. The default impl loops
over Self::add_edge — correct for every backend, fast
for none. Knowledge-graph batch ingest is the operator
hot path that motivates the override.
Sourcefn 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.
Asymmetric with Self::get_node (which returns
Option<N> because nodes have no separate structural
body) — the shape difference is intentional, not an
oversight.
Default impl returns None.
Sourcefn 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.
Sourcefn 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.
Sourcefn 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.
Edge-only by design — nodes have no timestamp on the
trait surface, so a TTL sweep cannot reason about them
directly. Nodes left orphaned by edge removal stay in
place until the operator drops them explicitly via a
future operation. This mirrors
crate::EntityMemory::prune_older_than and
crate::EpisodicMemory::prune_older_than (single
timestamp axis, no cascading semantics).
Default impl returns Ok(0) — only backends that own a
timestamp index implement this. Operators schedule it on
a timer (or trigger from a periodic graph) to bound
edge-table growth in long-running deployments.