docs: update README/INTERNALS for import feature, harden .gitignore
This commit is contained in:
122
decay_engine.py
122
decay_engine.py
@@ -70,93 +70,102 @@ class DecayEngine:
|
||||
# Permanent buckets never decay / 固化桶永远不衰减
|
||||
# ---------------------------------------------------------
|
||||
# ---------------------------------------------------------
|
||||
# Time weight: 0-1d→1.0, day2→0.9, then ~10%/day, floor 0.3
|
||||
# 时间系数:0-1天=1.0,第2天=0.9,之后每天约降10%,7天后稳定在0.3
|
||||
# Freshness bonus: continuous exponential decay
|
||||
# 新鲜度加成:连续指数衰减
|
||||
# bonus = 1.0 + 1.0 × e^(-t/36), t in hours
|
||||
# t=0 → 2.0×, t≈25h(半衰) → 1.5×, t≈72h → ≈1.14×, t→∞ → 1.0×
|
||||
# ---------------------------------------------------------
|
||||
@staticmethod
|
||||
def _calc_time_weight(days_since: float) -> float:
|
||||
"""
|
||||
Piecewise time weight multiplier (multiplies base_score).
|
||||
分段式时间权重系数,作为 final_score 的乘数。
|
||||
Freshness bonus multiplier: 1.0 + e^(-t/36), t in hours.
|
||||
新鲜度加成乘数:刚存入×2.0,~36小时半衰,72小时后趋近×1.0。
|
||||
"""
|
||||
if days_since <= 1.0:
|
||||
return 1.0
|
||||
elif days_since <= 2.0:
|
||||
# Linear interpolation: 1.0→0.9 over [1,2]
|
||||
return 1.0 - 0.1 * (days_since - 1.0)
|
||||
else:
|
||||
# Exponential decay from 0.9, floor at 0.3
|
||||
# k = ln(3)/5 ≈ 0.2197 so that at day 7 (5 days past day 2) → 0.3
|
||||
raw = 0.9 * math.exp(-0.2197 * (days_since - 2.0))
|
||||
return max(0.3, raw)
|
||||
hours = days_since * 24.0
|
||||
return 1.0 + 1.0 * math.exp(-hours / 36.0)
|
||||
|
||||
def calculate_score(self, metadata: dict) -> float:
|
||||
"""
|
||||
Calculate current activity score for a memory bucket.
|
||||
计算一个记忆桶的当前活跃度得分。
|
||||
|
||||
Formula: final_score = time_weight × base_score
|
||||
base_score = Importance × (act_count^0.3) × e^(-λ×days) × (base + arousal×boost)
|
||||
time_weight is the outer multiplier, takes priority over emotion factors.
|
||||
New model: short-term vs long-term weight separation.
|
||||
新模型:短期/长期权重分离。
|
||||
- Short-term (≤3 days): time_weight dominates, emotion amplifies
|
||||
- Long-term (>3 days): emotion_weight dominates, time decays to floor
|
||||
短期(≤3天):时间权重主导,情感放大
|
||||
长期(>3天):情感权重主导,时间衰减到底线
|
||||
"""
|
||||
if not isinstance(metadata, dict):
|
||||
return 0.0
|
||||
|
||||
# --- Pinned/protected buckets: never decay, importance locked to 10 ---
|
||||
# --- 固化桶(pinned/protected):永不衰减,importance 锁定为 10 ---
|
||||
if metadata.get("pinned") or metadata.get("protected"):
|
||||
return 999.0
|
||||
|
||||
# --- Permanent buckets never decay / 固化桶永不衰减 ---
|
||||
# --- Permanent buckets never decay ---
|
||||
if metadata.get("type") == "permanent":
|
||||
return 999.0
|
||||
|
||||
# --- Feel buckets: never decay, fixed moderate score ---
|
||||
if metadata.get("type") == "feel":
|
||||
return 50.0
|
||||
|
||||
importance = max(1, min(10, int(metadata.get("importance", 5))))
|
||||
activation_count = max(1, int(metadata.get("activation_count", 1)))
|
||||
|
||||
# --- Days since last activation / 距离上次激活过了多少天 ---
|
||||
# --- Days since last activation ---
|
||||
last_active_str = metadata.get("last_active", metadata.get("created", ""))
|
||||
try:
|
||||
last_active = datetime.fromisoformat(str(last_active_str))
|
||||
days_since = max(0.0, (datetime.now() - last_active).total_seconds() / 86400)
|
||||
except (ValueError, TypeError):
|
||||
days_since = 30 # Parse failure → assume 30 days / 解析失败假设已过 30 天
|
||||
days_since = 30
|
||||
|
||||
# --- Emotion weight: continuous arousal coordinate ---
|
||||
# --- 情感权重:基于连续 arousal 坐标计算 ---
|
||||
# Higher arousal → stronger emotion → higher weight → slower decay
|
||||
# arousal 越高 → 情感越强烈 → 权重越大 → 衰减越慢
|
||||
# --- Emotion weight ---
|
||||
try:
|
||||
arousal = max(0.0, min(1.0, float(metadata.get("arousal", 0.3))))
|
||||
except (ValueError, TypeError):
|
||||
arousal = 0.3
|
||||
emotion_weight = self.emotion_base + arousal * self.arousal_boost
|
||||
|
||||
# --- Time weight (outer multiplier, highest priority) ---
|
||||
# --- 时间权重(外层乘数,优先级最高)---
|
||||
# --- Time weight ---
|
||||
time_weight = self._calc_time_weight(days_since)
|
||||
|
||||
# --- Base score = Importance × act_count^0.3 × e^(-λ×days) × emotion ---
|
||||
# --- 基础得分 ---
|
||||
# --- Short-term vs Long-term weight separation ---
|
||||
# 短期(≤3天):time_weight 占 70%,emotion 占 30%
|
||||
# 长期(>3天):emotion 占 70%,time_weight 占 30%
|
||||
if days_since <= 3.0:
|
||||
# Short-term: time dominates, emotion amplifies
|
||||
combined_weight = time_weight * 0.7 + emotion_weight * 0.3
|
||||
else:
|
||||
# Long-term: emotion dominates, time provides baseline
|
||||
combined_weight = emotion_weight * 0.7 + time_weight * 0.3
|
||||
|
||||
# --- Base score ---
|
||||
base_score = (
|
||||
importance
|
||||
* (activation_count ** 0.3)
|
||||
* math.exp(-self.decay_lambda * days_since)
|
||||
* emotion_weight
|
||||
* combined_weight
|
||||
)
|
||||
|
||||
# --- final_score = time_weight × base_score ---
|
||||
score = time_weight * base_score
|
||||
# --- Weight pool modifiers ---
|
||||
# resolved + digested (has feel) → accelerated fade: ×0.02
|
||||
# resolved only → ×0.05
|
||||
# 已处理+已消化(写过feel)→ 加速淡化:×0.02
|
||||
# 仅已处理 → ×0.05
|
||||
resolved = metadata.get("resolved", False)
|
||||
digested = metadata.get("digested", False) # set when feel is written for this memory
|
||||
if resolved and digested:
|
||||
resolved_factor = 0.02
|
||||
elif resolved:
|
||||
resolved_factor = 0.05
|
||||
else:
|
||||
resolved_factor = 1.0
|
||||
urgency_boost = 1.5 if (arousal > 0.7 and not resolved) else 1.0
|
||||
|
||||
# --- Weight pool modifiers / 权重池修正因子 ---
|
||||
# Resolved events drop to 5%, sink to bottom awaiting keyword reactivation
|
||||
# 已解决的事件权重骤降到 5%,沉底等待关键词激活
|
||||
resolved_factor = 0.05 if metadata.get("resolved", False) else 1.0
|
||||
# High-arousal unresolved buckets get urgency boost for priority surfacing
|
||||
# 高唤醒未解决桶额外加成,优先浮现
|
||||
urgency_boost = 1.5 if (arousal > 0.7 and not metadata.get("resolved", False)) else 1.0
|
||||
|
||||
return round(score * resolved_factor * urgency_boost, 4)
|
||||
return round(base_score * resolved_factor * urgency_boost, 4)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Execute one decay cycle
|
||||
@@ -180,17 +189,41 @@ class DecayEngine:
|
||||
|
||||
checked = 0
|
||||
archived = 0
|
||||
auto_resolved = 0
|
||||
lowest_score = float("inf")
|
||||
|
||||
for bucket in buckets:
|
||||
meta = bucket.get("metadata", {})
|
||||
|
||||
# Skip permanent / pinned / protected buckets
|
||||
# 跳过固化桶和钉选/保护桶
|
||||
if meta.get("type") == "permanent" or meta.get("pinned") or meta.get("protected"):
|
||||
# Skip permanent / pinned / protected / feel buckets
|
||||
# 跳过固化桶、钉选/保护桶和 feel 桶
|
||||
if meta.get("type") in ("permanent", "feel") or meta.get("pinned") or meta.get("protected"):
|
||||
continue
|
||||
|
||||
checked += 1
|
||||
|
||||
# --- Auto-resolve: imp≤4 + >30 days old + not resolved → auto resolve ---
|
||||
# --- 自动结案:重要度≤4 + 超过30天 + 未解决 → 自动 resolve ---
|
||||
if not meta.get("resolved", False):
|
||||
imp = int(meta.get("importance", 5))
|
||||
last_active_str = meta.get("last_active", meta.get("created", ""))
|
||||
try:
|
||||
last_active = datetime.fromisoformat(str(last_active_str))
|
||||
days_since = (datetime.now() - last_active).total_seconds() / 86400
|
||||
except (ValueError, TypeError):
|
||||
days_since = 999
|
||||
if imp <= 4 and days_since > 30:
|
||||
try:
|
||||
await self.bucket_mgr.update(bucket["id"], resolved=True)
|
||||
auto_resolved += 1
|
||||
logger.info(
|
||||
f"Auto-resolved / 自动结案: "
|
||||
f"{meta.get('name', bucket['id'])} "
|
||||
f"(imp={imp}, days={days_since:.0f})"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Auto-resolve failed / 自动结案失败: {e}")
|
||||
|
||||
try:
|
||||
score = self.calculate_score(meta)
|
||||
except Exception as e:
|
||||
@@ -223,6 +256,7 @@ class DecayEngine:
|
||||
result = {
|
||||
"checked": checked,
|
||||
"archived": archived,
|
||||
"auto_resolved": auto_resolved,
|
||||
"lowest_score": lowest_score if checked > 0 else 0,
|
||||
}
|
||||
logger.info(f"Decay cycle complete / 衰减周期完成: {result}")
|
||||
|
||||
Reference in New Issue
Block a user