symfonic.memory.graph.store_queries¶
store_queries ¶
The graph store's read surface: filtered node queries and label aggregation.
Split out of :mod:symfonic.memory.graph.store (410 lines against the
300-line budget). What stayed behind is the write surface and the single-node
reads that also bump access counts. What moved here is the querying half --
query_nodes with its filter matrix, the subtree read and erase, and the
label aggregation the admin surfaces use -- which shares only _backend with
the rest.
GraphMemoryStore mixes this in, so every method is called as before.
GraphQueryMixin ¶
Filtered reads over a :class:GraphBackend.
_backend is supplied by the host store; it is declared here so the
mixin's reads are typed rather than implicit.
delete_subtree
async
¶
Erase scope and every descendant scope. Returns the node count.
Forwarded unchanged, and deliberately without the retraction filter every read on this wrapper applies. Erasure is not retrieval: a retracted memory is still a stored one, and a privacy sweep that skipped it would leave behind exactly the rows a subject asked to have removed while reporting success.
Forwarded at all because this wrapper is what the factories construct
and what MemoryOrchestrator holds, so it is where production wiring
reaches for a sweep. Without this method the only way to get one is to
reach past the wrapper to the raw backend, which silently skips the
access-count and retraction behaviour its sibling reads depend on.
Source code in src/symfonic/memory/graph/store_queries.py
distinct_labels
async
¶
Get distinct labels with counts and last_updated.
Aggregates all nodes in the tenant scope by their label field.
Soft-retracted nodes are excluded by default so a label whose only
node has been retracted is NOT reported as present -- otherwise a
required-memory discovery check (retrieval.discovery) would mark a
block satisfied when no live node backs it. Pass
include_retracted=True for audit aggregation.
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
List of dicts with keys |
Source code in src/symfonic/memory/graph/store_queries.py
query_nodes
async
¶
query_nodes(scope: TenantScope, layer: MemoryLayer | None = None, label: str | None = None, label_prefix: str | None = None, filters: dict[str, object] | None = None, limit: int | None = None, include_retracted: bool = False) -> list[MemoryNode]
Query nodes by layer, label, label_prefix, and/or arbitrary filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
TenantScope
|
Tenant isolation scope. |
required |
layer
|
MemoryLayer | None
|
Exact match on the memory layer. |
None
|
label
|
str | None
|
Exact match on the node label. |
None
|
label_prefix
|
str | None
|
Prefix match on the node label (e.g. |
None
|
filters
|
dict[str, object] | None
|
Arbitrary key/value filters passed through to the backend. |
None
|
limit
|
int | None
|
Maximum rows to return. |
None
|
include_retracted
|
bool
|
When |
False
|
A positive limit combined with include_retracted=False no
longer under-returns when a retracted row happens to fall inside the
backend's page: the wrapper over-fetches (doubling the requested
window) and refills with live nodes until limit is satisfied or
the backend is exhausted, then trims to exactly limit. This keeps
limit semantics honest for every backend (InMemory/Postgres/
Mongo) without any backend-side change.
Source code in src/symfonic/memory/graph/store_queries.py
query_subtree
async
¶
query_subtree(scope: TenantScope, filters: dict[str, object] | None = None, limit: int | None = None, include_retracted: bool = False) -> list[MemoryNode]
Read scope and every descendant scope — the mirror of the sweep.
Unlike :meth:delete_subtree this does filter retracted rows by
default, because it is a read and every other read here does. The two
differ for the reason the pair exists: the erase verb must reach
everything stored, the read verb must not surface what was corrected.