Archived
ci: make official engram build store-enabled (publish + link engram_store.{c,h}) #95
@@ -7835,14 +7835,78 @@ static int eg_edge_exists_between(EngramStore* g, const char* a, const char* b)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* engram_temporal_decay — recency shaping on the activation path.
|
||||||
|
*
|
||||||
|
* MEASURED FAILURE (2026-08-05 self-review). Census of the live graph under
|
||||||
|
* the previous form (uniform 168 h half-life, floor 0.05):
|
||||||
|
*
|
||||||
|
* node type n median tdecay % pinned at the 0.05 floor
|
||||||
|
* Memory 1233 0.0500 81%
|
||||||
|
* Knowledge 1183 0.0500 58%
|
||||||
|
* BacklogItem 1057 0.0500 91%
|
||||||
|
* Project 321 0.0500 98%
|
||||||
|
* Tag 135 0.0500 100%
|
||||||
|
*
|
||||||
|
* The median value for EVERY node type was the clamp. A function whose median
|
||||||
|
* output is its floor is not a signal — it is a constant with exceptions, and
|
||||||
|
* the exceptions were exactly the nodes touched in the last few days.
|
||||||
|
*
|
||||||
|
* What that cost, concretely: 10 of the 13 grounded value nodes — "Precision
|
||||||
|
* Over Brute Force", "Honesty Before Comfort", "The System Must Accumulate" —
|
||||||
|
* sat at 0.05, a 20x activation penalty, while Knowledge ingested overnight
|
||||||
|
* sat near 1.0 and held the working-memory top slots. The decay function was
|
||||||
|
* quietly erasing the accumulated library in favour of whatever arrived last
|
||||||
|
* night. That is a direct inversion of the system's purpose.
|
||||||
|
*
|
||||||
|
* Worse, tdecay multiplies at EVERY hop (seed activation and each propagation
|
||||||
|
* step), so a 2-hop path through settled knowledge compounded to 0.05^2 =
|
||||||
|
* 0.0025. Old regions of the graph were not disfavoured; they were unreachable.
|
||||||
|
*
|
||||||
|
* EXTERNAL EVIDENCE. "Not All Memories Age the Same" (arXiv:2604.26970)
|
||||||
|
* measures retrieval under different decay regimes:
|
||||||
|
*
|
||||||
|
* no temporal weighting NDCG@5 0.274
|
||||||
|
* uniform exponential decay NDCG@5 0.015 <- 18x WORSE than none
|
||||||
|
* domain-adaptive decay NDCG@5 0.241
|
||||||
|
* full adaptive hierarchy NDCG@5 0.260
|
||||||
|
*
|
||||||
|
* Uniform exponential decay is not merely suboptimal — it is worse than having
|
||||||
|
* no decay at all, because it penalises stable knowledge (rarely accessed,
|
||||||
|
* heavily load-bearing) while failing to suppress stale volatile facts. Notably
|
||||||
|
* not even the full adaptive hierarchy beat switching decay off.
|
||||||
|
*
|
||||||
|
* THE FIX: make the half-life a function of how established a node is, and
|
||||||
|
* make the floor a preference rather than a cliff.
|
||||||
|
*
|
||||||
|
* T_eff = T_HALF * (1 + ln(1 + activation_count))
|
||||||
|
*
|
||||||
|
* Frequently-retrieved nodes age slowly; nodes nothing has ever asked for age
|
||||||
|
* at the original rate. This is the spacing effect and the Lindy property in
|
||||||
|
* one line, it is monotone and log-bounded (a 10,000-activation node gets only
|
||||||
|
* a ~10x longer half-life, not a permanent exemption), and it is built from
|
||||||
|
* activation_count — which is measured, unlike `tier`, whose assignments are
|
||||||
|
* inconsistent enough to be untrustworthy here (the values node is tagged
|
||||||
|
* Episodic).
|
||||||
|
*
|
||||||
|
* The floor moves 0.05 -> 0.25. Given the evidence that no decay outperforms
|
||||||
|
* uniform decay, the honest maximum penalty for age alone is 4x, not 20x. Age
|
||||||
|
* should express a preference for the recent; it should never make a region of
|
||||||
|
* the graph structurally unreachable.
|
||||||
|
*
|
||||||
|
* Explicit per-node temporal_decay_rate still overrides lambda (77 nodes carry
|
||||||
|
* one) — that path is untouched and remains the escape hatch for content that
|
||||||
|
* genuinely should expire fast. */
|
||||||
|
#define ENGRAM_DECAY_FLOOR 0.25
|
||||||
static double engram_temporal_decay(const EngramNode* n, int64_t now_ms) {
|
static double engram_temporal_decay(const EngramNode* n, int64_t now_ms) {
|
||||||
int64_t age_ms = now_ms - n->last_activated;
|
int64_t age_ms = now_ms - n->last_activated;
|
||||||
if (age_ms <= 0) return 1.0;
|
if (age_ms <= 0) return 1.0;
|
||||||
double lambda = (n->temporal_decay_rate > 0.0) ? n->temporal_decay_rate
|
double lambda = (n->temporal_decay_rate > 0.0) ? n->temporal_decay_rate
|
||||||
: ENGRAM_DECAY_LAMBDA;
|
: ENGRAM_DECAY_LAMBDA;
|
||||||
double age_hours = (double)age_ms / 3600000.0;
|
double age_hours = (double)age_ms / 3600000.0;
|
||||||
double factor = exp(-lambda * age_hours / ENGRAM_T_HALF_HOURS);
|
double t_half = ENGRAM_T_HALF_HOURS *
|
||||||
if (factor < 0.05) factor = 0.05;
|
(1.0 + log(1.0 + (double)n->activation_count));
|
||||||
|
double factor = exp(-lambda * age_hours / t_half);
|
||||||
|
if (factor < ENGRAM_DECAY_FLOOR) factor = ENGRAM_DECAY_FLOOR;
|
||||||
return factor;
|
return factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user