symfonic.memory.janitor¶
janitor ¶
SemanticMerge -- deduplicates procedural memory nodes by semantic similarity.
Uses difflib.SequenceMatcher for label/content comparison so no additional dependencies are required. The default threshold of 0.65 captures cross-language similarities (e.g. "Ventas" vs "Sales") while still avoiding false positives on unrelated content.
SemanticMerge ¶
Bases: SemanticDeduplicationMixin
Deduplicates procedural nodes by semantic (textual) similarity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
threshold
|
float
|
Minimum similarity score (0.0–1.0) required for two nodes to be considered duplicates. Defaults to 0.75. |
0.65
|
Source code in src/symfonic/memory/janitor.py
deduplicate_all
async
¶
deduplicate_all(scope: TenantScope, graph_store: Any, *, audit: Any = None, context: Any = None) -> tuple[int, list[MemoryNode]]
The one audited bulk boundary; see _deduplicate_all_audited.
Source code in src/symfonic/memory/janitor.py
find_duplicates
async
¶
find_duplicates(scope: TenantScope, existing_nodes: list[MemoryNode], new_node: MemoryNode) -> list[MemoryNode]
Return nodes from existing_nodes that overlap with new_node.
A node is considered a duplicate when the similarity between its
representative text and that of new_node meets or exceeds
self.threshold. Nodes whose representative text is shorter than
_MIN_TEXT_LENGTH characters are skipped to avoid false positives
caused by incidental substring overlap in short identifiers.
v7.5 authored-tier guard (adopter Ask 7 Option B):
* When new_node itself is authored, return [] -- the
caller's store_skill call must produce a fresh graph
row even if a near-identical auto-promoted entry already
exists. Domain plugins own the authored skill's
lifecycle.
* Authored entries in existing_nodes are skipped as merge
candidates so an incoming auto-promoted draft cannot
absorb the authored canonical (which would mutate the
hand-edited fields).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
TenantScope
|
Effective exact owner permitted to merge both nodes. |
required |
existing_nodes
|
list[MemoryNode]
|
Candidate nodes already stored in the graph. |
required |
new_node
|
MemoryNode
|
The incoming node to compare against. |
required |
Returns:
| Type | Description |
|---|---|
list[MemoryNode]
|
Subset of existing_nodes whose similarity score >= threshold. |
Source code in src/symfonic/memory/janitor.py
merge
async
¶
Merge new into existing and return the canonical node.
Merge rules: - Reviewed canonical: An automatic Phase 12 draft cannot modify an existing approved or rejected procedure. - Label: Keep the longer label (assumed more specific / descriptive). - Steps: Union of both step lists, preserving order and removing exact duplicates. - Importance: Take the maximum of the two scores. - Lifecycle: takes the incoming explicit lifecycle, or retains the canonical lifecycle when the incoming row has none. Missing evidence is a draft/inactive row; drafts never become active by merging. - Properties: Shallow-merge; existing values are overridden by new values, except for the fields governed by the rules above. - Identity: The existing node's ID is preserved so callers can update the graph record in-place rather than creating a new one.
Source code in src/symfonic/memory/janitor.py
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 | |