symfonic.capabilities.memory.graph_store¶
graph_store ¶
The three memory ports, persisted through the shipped GraphBackend.
Persists MemoryNode rows through GraphBackend using compatible formats.
forget erases a scope subtree, retracted rows stay hidden until pruning,
and pending rows remain invisible until flush. Those rules are enforced
at the shared read/lifecycle choke points below rather than by callers.
Each node is updated and deleted at its own scope, reconstructed from the
stored scope_path. The backends gate mutation by ancestor visibility, so a
descendant's row cannot be written through its parent's scope -- which is the
correct rule for a read and would be a silent no-op here.
GraphBackedHms ¶
All three memory ports, over a graph backend and optionally a vector one.
Composed with vectors and embedder, recall runs both routes and
merges them: the graph answers what matches the cue's words, the vector
index what matches its meaning, and a query sharing no vocabulary with
the memory it needs is answered by the second. Composed without them,
nothing changes -- which is why they are one optional pair rather than a
second store class.
A pair because neither is useful alone: an embedder with nowhere to put a vector writes nothing, and a vector backend with no embedder cannot be queried. One without the other is refused at construction rather than discovered later as recall that was silently lexical.
Source code in src/symfonic/capabilities/memory/graph_store.py
graph
property
¶
The store or backend this HMS writes through.
Published for the consolidation commit, which must establish that the records it publishes land in the same transaction domain as the graph mutations it applies beside them.
transaction_participants
property
¶
Every backend a write/flush can mutate, including vector publication.
discard
async
¶
Delete the pending rows under scope; committed history survives.
Row-by-row rather than through delete_subtree, and deliberately:
the subtree sweep is the privacy verb and takes everything. A turn
taking back its own writes may not take the previous turns' with them,
so the pending predicate has to be part of the selection.
Source code in src/symfonic/capabilities/memory/graph_store.py
forget
async
¶
Erase scope and everything below it, pending or not.
The ids are read before the sweep because the receipt names them, not
because the sweep needs them: delete_subtree is one backend-native
statement over the whole subtree, so a row written into a descendant
scope between the read and the delete is still erased.
Source code in src/symfonic/capabilities/memory/graph_store.py
retrieve
async
¶
Both routes, merged by record_id and ranked once.
Source code in src/symfonic/capabilities/memory/graph_store.py
scan_candidates
async
¶
Every visible candidate, uncapped by query.limit (CandidateScan).
Not a call to :meth:retrieve: that one ends in select, which
applies the limit and the character ceilings, and those are exactly
the decisions a scan must leave to its caller.
Both routes, because this is the method a turn's hydration actually calls -- a scan that asked only the lexical route would leave the vector index composed, written to, and never consulted, which is the shape this pair exists to remove.
The vector half is bounded by its own top-k, which is the "store's own budget" this contract allows: a similarity search has no unbounded form, and asking for every vector in the scope would be a scan of the index rather than a search of it.
Source code in src/symfonic/capabilities/memory/graph_store.py
node_payload ¶
The mapping compat.record_from_legacy_node reads, off a real node.
Source code in src/symfonic/capabilities/memory/graph_rows.py
tenant_scope ¶
The legacy scope value for a capability scope. Kinds from compat.