symfonic.core.learning.phases_maintenance¶
phases_maintenance ¶
Moved to :mod:symfonic.capabilities.memory.phases.maintenance.
These phases are on a roster the kernel composes, so they live in the capability now and are imported back here for the legacy consolidator to call. One phase, one implementation, for as long as both routes ship.
The private helpers travel with them: the shipped per-phase tests import them by name, and a shim that re-exported only the public surface would have moved the code away from the tests that watch it.
cleanup_working_ttl
async
¶
cleanup_working_ttl(graph: GraphMemoryStore, scope: TenantScope, all_nodes: list[MemoryNode] | None = None) -> int
Prune WORKING-layer graph nodes with expired TTL.
Queries all WORKING-layer nodes and deletes those whose
ttl_hours property has elapsed relative to updated_at.
v7.26.2 (Shape C1) adds two durability-aware branches:
durability == "expired"-- pruned unconditionally (the explicit "this transient row served its purpose" signal fromupgrade_durability), bypassing all TTL math.durability == "transient"-- subject to a 1h implicit TTL floor. A transient row with nottl_hoursages out after 1h; a transient row with a longerttl_hoursis capped at 1h (Contract F). This protects against the "still syncing" footgun where a transient row withttl_hours=720would otherwise survive 30 days.
Nodes with no durability marker keep their pre-v7.26.2 behaviour
exactly: pruned only when they carry a ttl_hours that has elapsed.
Unpublished writes are exempt. A persistent adapter spells "written
but not yet flushed" as durability="transient" plus the namespaced
markers in :mod:symfonic.memory.pending, because that is the only value
the legacy reader already hides. It borrows the word, not the lifecycle:
a pending row is resolved by a flush or a discard, never by a clock. Left
unguarded, this phase would delete a turn's uncommitted writes an hour in
and the flush that followed would commit nothing, silently.
Returns:
| Type | Description |
|---|---|
int
|
Number of pruned nodes. |
Source code in src/symfonic/capabilities/memory/phases/maintenance.py
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | |
decay_importance
async
¶
decay_importance(graph: GraphMemoryStore, scope: TenantScope, all_nodes: list[MemoryNode], stale_days: int = STALE_DAYS_DEFAULT) -> int
Reduce importance of semantic nodes not accessed in stale_days.
Nodes whose updated_at is older than the threshold have their
importance reduced by 0.5, floored at 1.0.
High-signal / identity nodes are EXEMPT from decay so that the user
profile and agent persona are never corrupted by age-based attrition.
See :func:_is_exempt_from_decay for the exemption criteria.
Returns:
| Type | Description |
|---|---|
int
|
Number of decayed nodes. |
Source code in src/symfonic/capabilities/memory/phases/maintenance.py
is_identity_labelled ¶
Return True when node's label carries an identity prefix.
Label starts with SOUL: / SOUL (user profile data) or
AGENT_IDENTITY: / AGENT_IDENTITY (agent persona). Split out
of :func:_is_exempt_from_decay (importance-decay's exemption bundles
this with the importance >= CRITICAL_IMPORTANCE_THRESHOLD check)
so :mod:phases_semantic_expiry can reuse the label-only half without
also inheriting the importance-based exemption -- see that module's
docstring for why a high-importance node must NOT be exempt there.
Source code in src/symfonic/capabilities/memory/phases/maintenance.py
prune_retracted
async
¶
prune_retracted(graph: GraphMemoryStore, scope: TenantScope, grace_days: int = RETRACTED_GRACE_DAYS_DEFAULT) -> int
Hard-delete nodes soft-retracted more than grace_days ago.
A retract_node extraction op flags a false-positive/corrected memory
with the namespaced retraction marker + timestamp (see
symfonic.memory.retraction and SymfonicAgent._retract_memory). The
flag alone removes the node from every read path immediately
(GraphMemoryStore.query_nodes excludes it), but the row lingers for
reversibility/audit. This phase reclaims it once it has been retracted
past the grace window.
Must query with include_retracted=True -- retracted nodes are invisible
to the default read path by design, so a plain scan would never see them.
query_nodes is ancestor-visible (a child scope sees a parent scope's
rows), so an enumerated retracted node may belong to an ANCESTOR scope,
not this one. Only a node materialized at exactly scope is deleted;
ancestor-owned rows are left for the ancestor's own consolidation to
reclaim, otherwise a child's consolidation could hard-delete a parent's
memory.
Returns:
| Type | Description |
|---|---|
int
|
Number of pruned nodes. |