ci: make official engram build store-enabled (publish + link engram_store.{c,h}) #95

Merged
will.anderson merged 27 commits from engram-tiered-storage into dev 2026-08-12 20:23:37 +00:00
Showing only changes of commit 3d05e0c2a9 - Show all commits
+66 -2
View File
@@ -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;
}