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;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
int64_t age_ms = now_ms - n->last_activated;
|
||||
if (age_ms <= 0) return 1.0;
|
||||
double lambda = (n->temporal_decay_rate > 0.0) ? n->temporal_decay_rate
|
||||
: ENGRAM_DECAY_LAMBDA;
|
||||
double age_hours = (double)age_ms / 3600000.0;
|
||||
double factor = exp(-lambda * age_hours / ENGRAM_T_HALF_HOURS);
|
||||
if (factor < 0.05) factor = 0.05;
|
||||
double t_half = ENGRAM_T_HALF_HOURS *
|
||||
(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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user