symfonic.capabilities.memory.promotion¶
promotion ¶
Promotion: a memory moving toward the root, and the receipt for why.
Retrieval's visibility rule runs one way — a memory written at acme is
visible inside acme/alice/s1, never the reverse — precisely so that
publishing one session's content to a whole tenant is a decision somebody
takes rather than a side effect of a query. This module is that decision.
Two rules, and both are refusals:
- Only durable, corroborated facts promote. The 4-tier write model keeps
conversational state
transientand promotes extracted facts only; a confidence floor keeps a guess from becoming a tenant-wide truth. Same predicate as the shippedcore.learning.promotion.is_promotion_eligible. - Promotion moves toward the root. Promoting downward would republish a tenant fact as one principal's own, which is a scope violation wearing a policy's clothes, so it raises rather than reporting.
Provenance is the other half. A promoted fact carries the conversations that
produced it, as arrays from the start, because a durable fact is normally
corroborated more than once — and because erasure is reference-counted:
removing the last source conversation marks the memory for deletion, while a
surviving corroboration keeps it. The dict shape is byte-identical to the
shipped one (source_conversation_ids, source_scope_paths,
promoted_at, promoted_by, extraction_confidence) so a node promoted
by either side is readable by both.
Promotion
dataclass
¶
A promoted memory and the provenance that justifies it.
PromotionCandidate
dataclass
¶
PromotionCandidate(record: MemoryRecord, confidence: float = 0.0, durability: str = _PROMOTABLE_DURABILITY, conversation_id: str = '')
One memory considered for promotion, with the signals that decide it.
apply_erasure ¶
apply_erasure(provenance: dict[str, Any] | None, erased_conversation_id: str, *, pii_policy: str = PII_POLICY_DELETE) -> tuple[dict[str, Any] | None, bool]
Remove one conversation from a provenance; report whether to delete.
Reference-counted rather than cascading: a fact corroborated by two conversations survives the erasure of one. When the last source goes, the default policy marks the memory for deletion — no orphaned personal data survives as an anonymous "fact".
Source code in src/symfonic/capabilities/memory/promotion.py
build_provenance ¶
build_provenance(*, source_conversation_id: str, source_scope_path: str, promoted_by: str, extraction_confidence: float, promoted_at: str | None = None) -> dict[str, Any]
The provenance a newly promoted memory carries.
Source code in src/symfonic/capabilities/memory/promotion.py
is_promotable ¶
is_promotable(candidate: PromotionCandidate, *, confidence_floor: float = DEFAULT_PROMOTION_CONFIDENCE_FLOOR) -> bool
Whether candidate may be published to a broader scope.
Source code in src/symfonic/capabilities/memory/promotion.py
merge_provenance ¶
merge_provenance(existing: dict[str, Any] | None, *, source_conversation_id: str, source_scope_path: str, promoted_by: str, extraction_confidence: float) -> dict[str, Any]
Append a corroborating conversation to an existing provenance.
The highest confidence observed wins and the latest promotion time stands: a second conversation confirming a fact makes it more trustworthy, and taking the newer (possibly lower) confidence would let one weak restatement demote a well-established memory.
Source code in src/symfonic/capabilities/memory/promotion.py
promote ¶
promote(candidate: PromotionCandidate, target: MemoryScope, *, promoted_by: str, promoted_at: str | None = None) -> Promotion
Restate candidate's memory at target, with its provenance.
The record id is kept. Ids are the upsert key within a scope, so re-promoting the same fact overwrites its own earlier promotion instead of accumulating near-duplicates of it, which is what makes running consolidation twice harmless.