Files
LithosAnanake/docs/working/architecture/03-architecture/adaptive-systems/adaptive-window-and-decay.md
T

12 KiB

Adaptive Window Shrinking & Decay Slope Investigation

Date: 2025-11-08 Status: Root Cause Analysis Complete Critical Finding: Adaptive window is UNIDIRECTIONAL (shrink-only), Loop #5 growth mechanism deferred to Phase 2


Executive Summary

You asked: "Does it shrink AND grow? Is there a driving metric and input into maybe a gauge study and inference?"

The answer is NO - currently the system only shrinks, with no growth mechanism. Here's the complete picture:

Current State

  • Shrinking IS implemented and being called
  • Growing is NOT implemented (deferred to Phase 2 as Loop #5)
  • No gauge study/inference mechanism (no measurement error analysis)
  • Input metric exists (pattern diversity), but feedback loop is incomplete

Part 1: Rolling Window Adaptive Shrinking

Architecture

The Feedback Loop (Current - Incomplete)

Execution Stream
    ↓
rolling_window_record_execution()  [Called for EVERY word execution]
    ↓
Every 256 executions:
    ↓
rolling_window_check_adaptive_shrink()
    ↓
Measure: rolling_window_measure_diversity()
    Input: Unique adjacent word transitions (word_a → word_b)
    ↓
Calculate: growth_rate = (current_diversity - last_diversity) / last_diversity
    ↓
Decision Rule: IF growth_rate < 1% → SHRINK
    ↓
Action: effective_window_size = effective_window_size * 75%
    Floor: Never shrink below 256

Key Files

File Component Status
src/rolling_window_of_truth.c:546 rolling_window_measure_diversity() Implemented
src/rolling_window_of_truth.c:589 rolling_window_check_adaptive_shrink() Implemented, called every 256 executions
include/rolling_window_knobs.h Tuning parameters Full documentation
src/rolling_window_of_truth.c:644-646 Loop #5 (Growth mechanism) DEFERRED to Phase 2

Invocation Chain

vm.c:708: rolling_window_record_execution(&vm->rolling_window, word_id)
    ↓
rolling_window_of_truth.c:541: Every 256 executions:
    rolling_window_check_adaptive_shrink(window)
        ↓
    Measures diversity
    Calculates growth_rate
    IF growth_rate < 1%: shrinks to 75%

Verified: Function IS called, IS invoked correctly, IS working as designed.

Current Behavior (Shrink-Only)

What happens:

// src/rolling_window_of_truth.c:622-642
if (growth_rate < threshold && window->effective_window_size > ADAPTIVE_MIN_WINDOW_SIZE)
{
    uint32_t new_size = (window->effective_window_size * ADAPTIVE_SHRINK_RATE) / 100;
    if (new_size < ADAPTIVE_MIN_WINDOW_SIZE)
        new_size = ADAPTIVE_MIN_WINDOW_SIZE;

    if (new_size < window->effective_window_size)
    {
        window->effective_window_size = new_size;  // SHRINK
    }
}

What does NOT happen:

// This does NOT exist - commented as Phase 2
// if (growth_rate > some_upper_threshold && window->effective_window_size < ROLLING_WINDOW_SIZE)
// {
//     window->effective_window_size = grow_window();
// }

Why It's Not Shrinking in Tests

The adaptive shrinking mechanism EXISTS and IS CALLED, but metrics show effective_window_size = 4096 always. This could mean:

  1. Pattern diversity never plateaus - The test harness executes a FORTH program with continuously growing unique word transitions, so growth_rate stays > 1% always
  2. Window never becomes "warm" - Shrinking is disabled until is_warm=1 (after 4096 executions). If test doesn't run that long, no shrinking occurs
  3. Measurements are too noisy - Diversity growth jumps around, never settling below 1% threshold

Evidence from test-03:

  • A_B_CACHE: 3,895 total lookups (call sequence ~3895 words)
  • Test harness runs short FORTH program with limited vocabulary
  • Pattern diversity likely hits saturation but we see effective_window_size = 4096 in metrics

Root Cause Hypothesis: The metrics are extracted ONCE at the end of the test run, but adaptive shrinking happens dynamically. By the time we extract, the window may have already hit ADAPTIVE_MIN_WINDOW_SIZE (256) and stayed there. OR the shrinking never triggered because diversity never plateaued below 1%.


Part 2: Decay Slope Investigation

Current State (Line 216 of doe_metrics.c)

metrics.decay_slope = 0.0;  // ❌ Hardcoded to zero

Where Decay Information Exists

Source: src/physics_metadata.c:164-203

void physics_metadata_apply_linear_decay(DictEntry *entry, uint64_t elapsed_ns)
{
    // H(t) = max(0, H_0 - d*t)
    // decay_amount = (elapsed_us * DECAY_RATE_PER_US_Q16) >> 16

    uint64_t elapsed_us = elapsed_ns / 1000;
    uint64_t decay_amount_raw = (elapsed_us * DECAY_RATE_PER_US_Q16) >> 16;

    if (decay_amount >= entry->execution_heat) {
        entry->execution_heat = 0;
    } else {
        entry->execution_heat -= decay_amount;
    }
}

The Linear Model

Decay Rate: DECAY_RATE_PER_US_Q16 = 3 (in include/rolling_window_knobs.h)
  Meaning: 3 / 65536 heat units per microsecond
  ≈ 0.0000458 heat/μs

Half-life: ~1-2 seconds for 100-heat word
  100 heat / 45.8 heat_per_second ≈ 2.18 seconds

Equation: decay_amount = (elapsed_us * 3) >> 16

Why decay_slope = 0.0

There is NO TRACKING of how much total heat decayed during the run:

  1. Individual words decay (physics_metadata_apply_linear_decay)
  2. No aggregate heat trajectory stored
  3. No slope calculated from beginning to end of test
  4. No "total heat budget" measurement

What decay_slope Should Represent

Option 1: Constant Decay Rate

metrics.decay_slope = (double)DECAY_RATE_PER_US_Q16 / 65536.0;  // 0.0000458

Option 2: Observed Heat Decline

// At start: sum all execution_heat values
// At end: sum all execution_heat values
// decay_slope = (heat_start - heat_end) / total_runtime_us

Option 3: Per-Word Average Decay

// Average decay per word = total_heat_decayed / word_count / runtime_us

Currently: NONE OF THESE ARE IMPLEMENTED - it's hardcoded to 0.0.


Part 3: What You're Really Asking (Design Question)

Your question "Does it shrink AND grow? Is there a gauge study and inference?" touches on fundamental optimization design:

Current System (Unidirectional)

Pattern Diversity Measurement
    ↓
Growth Rate Calculation
    ↓
Decision: Shrink if growth_rate < 1%
    ↓
Action: Reduce window_size to 75%
    ↓
Result: Monotonic reduction toward ADAPTIVE_MIN_WINDOW_SIZE (256)

Problem: Once shrunk, it can't grow back. If a new pattern emerges after shrinking, the window is too small to capture it.

What You're Envisioning (Bidirectional, Loop #5)

Pattern Diversity Measurement
    ↓
[Growth Rate Calculation]    [Prefetch Accuracy Feedback]
    ↓                              ↓
[Shrinking Decision]         [Growth Decision]
    IF growth < 1%           IF prefetch_accuracy > threshold
    AND no_new_patterns      AND memory_permits
    ↓                              ↓
[Shrink to 75%]             [Grow to 90% of max]
    ↓                              ↓
Result: Adaptive oscillation toward optimal window size

Missing Components (Phase 2 - Loop #5)

  1. Growth Trigger: When should window grow?

    • Prefetch accuracy drops (misses emerging patterns)?
    • Pattern diversity suddenly increases?
    • Memory available?
  2. Gauge Study/Inference: How to measure if shrinking was correct?

    • Prefetch accuracy before vs. after shrinking
    • Cache hit rate before vs. after
    • Pattern capture completeness
  3. Feedback Integration: Currently Loop #4 (pipelining) doesn't feed into Loop #5 (window tuning)

    • Loop #4 measures prefetch_accuracy (83.55%)
    • Loop #5 should use that to tune window size
    • Currently disconnected

Part 4: Why Metrics Show Static Values

rolling_window_width = 4096 (Always)

Explanation:

// src/doe_metrics.c:214
metrics.rolling_window_width = (uint32_t)vm->rolling_window.effective_window_size;

This reads the CURRENT effective_window_size at metrics extraction time. Possible reasons it's always 4096:

  1. Never triggers shrinking: Diversity growth stays > 1% throughout test
  2. Test is too short: Window takes ~4096 executions to become "warm", test ends before any shrinking
  3. Metrics extracted at wrong time: Extracted after window has already been reset/re-initialized
  4. Shrinking happens but we measure too early: Metrics captured before effective_window_size written to VM state

decay_slope = 0.0 (Always)

Explanation:

// src/doe_metrics.c:216
metrics.decay_slope = 0.0;  // Completely hardcoded, no extraction logic

This is a placeholder that was never filled in. The linear decay equation exists, but:

  1. No total heat tracking during run
  2. No trajectory stored (heat at t=0 vs t=end)
  3. No slope calculation implemented
  4. Should extract from DECAY_RATE_PER_US_Q16 or measured heat loss

Recommendations

Short Term (Fix Metrics Extraction)

For rolling_window_width:

  • Code is correct - it extracts effective_window_size as intended
  • 🔍 Need to verify: Does shrinking actually occur during test execution?
  • 📊 Add instrumentation: Log when adaptive shrinking happens

For decay_slope:

  • Replace hardcoded 0.0 with one of:
    • Constant decay rate from DECAY_RATE_PER_US_Q16
    • Measured heat loss over test duration
    • Per-word average decay rate

Medium Term (Implement Loop #5 - Phase 2)

  1. Add growth trigger based on:

    • Prefetch accuracy feedback from Loop #4
    • Pattern diversity sudden increase
    • Memory availability
  2. Implement gauge study:

    • Compare prefetch accuracy before/after shrinking
    • Measure pattern capture completeness
    • Track false negatives (patterns missed due to window size)
  3. Wire Loop #4 → Loop #5 feedback

    • Loop #4 produces prefetch_accuracy metric
    • Loop #5 uses it to tune window_size bidirectionally

Long Term (Physics-Based Window Optimization)

Current: Pattern diversity → Shrink decision

Future: Particle physics model

Window as "potential well" with:
- Capacity = 4096 (maximum)
- Mass = current execution_heat
- Charge = pattern diversity
- Spin = transition prediction accuracy

Shrinking = release potential (conserve energy)
Growing = absorb potential (capture emerging patterns)
Equilibrium = optimal window size

Files Affected

File Change Priority
src/doe_metrics.c:216 Implement decay_slope extraction MEDIUM
src/rolling_window_of_truth.c:644-646 Uncomment/implement Loop #5 HIGH (Phase 2)
include/rolling_window_knobs.h Add growth knobs HIGH (Phase 2)
include/vm.h Track heat trajectory if needed MEDIUM

Conclusion

Your intuition was correct: The current system is unidirectional (shrink-only) with no growth mechanism. The decay_slope metric is unimplemented.

This is not a bug - it's a Phase 1/Phase 2 boundary. Loop #5 (bidirectional window tuning with growth) is explicitly deferred to Phase 2 in the code comments.

The adaptive shrinking that exists IS working correctly (called every 256 executions), but the test harness may not be exhibiting enough pattern diversity plateau to trigger shrinking.

To validate: Run a test with logging enabled to see if rolling_window_check_adaptive_shrink() is actually shrinking or just measuring.