symfonic.capabilities.memory.phases.strengthen¶
strengthen ¶
Phase 1: a memory that keeps coming up matters more than one that does not.
Moved here from symfonic.core.learning.phases rather than reimplemented.
The rule the move follows is the one the roster tables already state: a phase
that is transcribed twice is a phase with two behaviours, and the second one
is discovered by an adopter. core.learning.phases.strengthen is now this
function, imported, so the legacy consolidator and the capability runtime
cannot drift apart while both are shipped.
The body is unchanged. What follows is why it looks the way it does.
strengthen
async
¶
strengthen(graph: GraphMemoryStore, scope: TenantScope, recent_nodes: list[MemoryNode], *, spreading_weight: float = 0.5) -> int
Increment importance on recently accessed (recurring) nodes.
A node qualifies as "recurring" when EITHER:
-
It appears >= 2 times in
recent_nodes(duplicate-in-list signal). This covers access-log replay / neighbourhood traversal callers that intentionally feed per-hit records into the consolidator. -
Its combined recurrence score
access_count + spreading_weight * spreading_access_count >= 2. -
access_countis bumped byGraphMemoryStore.get_nodeon every direct fetch (one-shot reads). spreading_access_countis bumped byGraphMemoryStore.bump_spreadingon every BFS-induced visit (spreading activation throughGraphTraversal.bfs).
spreading_weight (default 0.5) comes from
FrameworkConfig.phase1_spreading_weight. At 0.5 the combined
score matches v6.1.x observable behaviour: two BFS visits count
like one direct fetch. Setting the weight to 0.0 yields the
direct-only semantic from the user task description.
Before the v6.0.x fix, Phase 1 silently never fired in production:
GraphMemoryStore.query_nodes returns each node exactly once, so
the duplicate-in-list count was always 1 and the < 2 guard always
skipped. Falling back to the per-node access_count surfaces the
access-recurrence signal already captured at retrieval time without
requiring a new DB schema or access log.
The count semantics of the list-duplicate path are preserved: a node that appears N>=2 times in the list still gets N successful update attempts (each subsequent read loads the freshly-boosted value).
ADR (v6.1 T02/T03, revised v6.2 T02): the canonical recurrence signal
is the combined access_count + weighted spreading_access_count.
v6.1 aligned spreading-activation reads with the single access_count
counter by routing GraphTraversal.bfs through store.get_node.
v6.2 decomposed the counter so callers can distinguish direct fetches
from one-shot spreading visits. BFS now bumps
spreading_access_count via GraphMemoryStore.bump_spreading.
Intentionally excluded from bumping: query_nodes (returns
candidate sets for scoring -- one query bumping N nodes would distort
the frequency signal that scoring.py also reads), and
shortest_path / graph_proximity (run inside the scoring
loop itself -- same contamination risk).