11 KiB
L8 Control Wiring Implementation Plan
Status: Infrastructure exists but execution paths are not gated Date: 2025-12-05 Issue: L8 Jacquard mode selector computes 4-bit control signals (L2/L3/L5/L6) but feedback loops ignore them
Executive Summary
The L8 Steady State Machine (SSM) infrastructure is 90% complete but the final 10% is critical:
✅ Complete:
- L8 state machine (
ssm_jacquard.c) computes 4-bit mode (C0-C15) based on entropy/CV/temporal metrics ssm_apply_mode()sets L2/L3/L5/L6 flags invm->ssm_configvm_heartbeat_update_l8()updates L8 state every heartbeat cycle (lines 993-1045 invm.c)- Mode transitions logged with debug info
❌ Missing:
- Feedback loop execution paths don't check
ssm_configflags before running - L2/L3/L5/L6 run unconditionally despite L8 setting mode bits
- Result: L8 is a "supervisor without authority"
Current Architecture (From DoE Analysis)
From Makefile:112-127 and ssm_jacquard.h:32-51:
| Loop | Name | Status | Control |
|---|---|---|---|
| L1 | Heat Tracking | ALWAYS OFF | Disabled (harmful in 86% of configs) |
| L2 | Rolling Window | NEEDS GATING | L8 bit 3 (entropy-driven) |
| L3 | Linear Decay | NEEDS GATING | L8 bit 2 (temporal locality) |
| L4 | Pipelining Metrics | ALWAYS OFF | Disabled (harmful in 100% of configs) |
| L5 | Window Inference | NEEDS GATING | L8 bit 1 (CV-driven) |
| L6 | Decay Inference | NEEDS GATING | L8 bit 0 (CV + temporal) |
| L7 | Adaptive Heartrate | ALWAYS ON | Enabled (beneficial in 71% of configs) |
| L8 | Jacquard Selector | IMPLEMENTED | 4-bit mode controller (C0-C15) |
Key Insight: L1 and L4 are compile-time disabled. L7 is always-on. L2/L3/L5/L6 need runtime gating based on L8 mode bits.
Required Changes (4 Locations)
1. L2: Rolling Window Recording (src/rolling_window_of_truth.c)
Current Code (rolling_window_record_execution() line 181):
int rolling_window_record_execution(RollingWindowOfTruth* window, uint32_t word_id)
{
if (!window || !window->execution_history)
return -1;
window->execution_history[window->window_pos] = word_id;
// ... always executes
}
Required Change: Add check at function entry or call site. Two options:
Option A (Preferred - Clean separation):
Modify call site in src/vm.c:1652:
// Before (line 1652):
rolling_window_record_execution(&vm->rolling_window, word_id);
// After:
if (vm->ssm_config && ((ssm_config_t*)vm->ssm_config)->L2_rolling_window) {
rolling_window_record_execution(&vm->rolling_window, word_id);
}
Option B (Alternative - Gate inside function):
Modify rolling_window_record_execution() to accept VM pointer and check internally.
Files to modify:
src/vm.c:1652- Call site inexecute_word()src/rolling_window_of_truth.c:181- Consider adding VM* parameter for internal checkinclude/rolling_window_of_truth.h- Update function signature if Option B chosen
2. L3: Linear Decay Application (src/physics_metadata.c)
Current Code (line 176):
/* L8 FINAL INTEGRATION: L3 linear decay is always-on */
void physics_metadata_apply_linear_decay(DictEntry *entry, uint64_t elapsed_ns, VM *vm) {
if (!entry || !vm) {
return;
}
/* Frozen words do not decay */
if (entry->flags & WORD_FROZEN) {
return;
}
/* Don't bother with insignificant time intervals */
if (elapsed_ns < DECAY_MIN_INTERVAL) {
return;
}
// ... decay logic always executes
}
Required Change: Add L3 gate check after frozen/interval checks:
/* L8 FINAL INTEGRATION: L3 linear decay - runtime gated by Jacquard */
void physics_metadata_apply_linear_decay(DictEntry *entry, uint64_t elapsed_ns, VM *vm) {
if (!entry || !vm) {
return;
}
/* Frozen words do not decay */
if (entry->flags & WORD_FROZEN) {
return;
}
/* Don't bother with insignificant time intervals */
if (elapsed_ns < DECAY_MIN_INTERVAL) {
return;
}
/* L8 GATE: Check if L3 (linear decay) is enabled by Jacquard mode */
if (vm->ssm_config) {
ssm_config_t *config = (ssm_config_t*)vm->ssm_config;
if (!config->L3_linear_decay) {
return; /* L3 disabled by L8 mode selector */
}
}
// ... existing decay logic
}
Files to modify:
src/physics_metadata.c:176- Add L3 gate check (line ~188-193)- Comment update at line 175 to reflect runtime gating
Call sites (no changes needed, gate is internal):
src/vm.c:737-vm_tick_apply_background_decay()(heartbeat loop)- Other call sites inherit the gate
3. L5: Window Width Inference (src/inference_engine.c)
Current Code (line 538):
/* === PHASE 2C: Window Width Inference === */
/* L8 FINAL INTEGRATION: Loop #5 always-on */
uint32_t inferred_width = find_variance_inflection(
trajectory,
traj_len,
current_variance
);
Required Change: Gate inference execution with conditional block:
/* === PHASE 2C: Window Width Inference === */
/* L8 FINAL INTEGRATION: Loop #5 runtime-gated by Jacquard */
uint32_t inferred_width = outputs->adaptive_window_width; /* Keep previous value as default */
if (inputs->vm && inputs->vm->ssm_config) {
ssm_config_t *config = (ssm_config_t*)inputs->vm->ssm_config;
if (config->L5_window_inference) {
/* L5 enabled - run inference */
inferred_width = find_variance_inflection(
trajectory,
traj_len,
current_variance
);
}
/* else: L5 disabled, keep previous adaptive_window_width */
}
Files to modify:
src/inference_engine.c:538-544- Add L5 conditional gate- Comment update at line 539 to reflect runtime gating
4. L6: Decay Slope Inference (src/inference_engine.c)
Current Code (line 546):
/* === PHASE 2D: Decay Slope Inference === */
/* L8 FINAL INTEGRATION: Loop #6 always-on */
uint64_t inferred_slope = infer_decay_slope_q48(trajectory, traj_len);
Required Change: Gate inference execution with conditional block:
/* === PHASE 2D: Decay Slope Inference === */
/* L8 FINAL INTEGRATION: Loop #6 runtime-gated by Jacquard */
uint64_t inferred_slope = outputs->adaptive_decay_slope; /* Keep previous value as default */
if (inputs->vm && inputs->vm->ssm_config) {
ssm_config_t *config = (ssm_config_t*)inputs->vm->ssm_config;
if (config->L6_decay_inference) {
/* L6 enabled - run inference */
inferred_slope = infer_decay_slope_q48(trajectory, traj_len);
}
/* else: L6 disabled, keep previous adaptive_decay_slope */
}
Files to modify:
src/inference_engine.c:546-548- Add L6 conditional gate- Comment update at line 547 to reflect runtime gating
Implementation Order (Recommended)
-
L3 (Decay) - Simplest, internal to one function
- Modify
src/physics_metadata.c:176-220 - Add gate check after line 188
- Test with mode C0 (all off) vs C4 (L3 on)
- Modify
-
L5 (Window Inference) - Medium complexity
- Modify
src/inference_engine.c:538-544 - Preserve previous value when disabled
- Test early-exit behavior with mode C0 vs C2
- Modify
-
L6 (Decay Inference) - Similar to L5
- Modify
src/inference_engine.c:546-548 - Preserve previous value when disabled
- Test with mode C0 vs C1
- Modify
-
L2 (Rolling Window) - Most complex (data structure)
- Decide on Option A (call-site gate) vs Option B (internal gate)
- Modify call site
src/vm.c:1652OR function signature - Test with mode C0 vs C8 (high entropy)
Testing Strategy
Unit Tests
For each loop, verify:
- Mode C0 (0000): All loops disabled → function returns early or skips logic
- Single-bit modes: Enable one loop at a time (C1, C2, C4, C8)
- Top 5% modes: C4, C7, C9, C11, C12 work correctly
Integration Tests
- Mode transitions: L8 changes mode → loops respond correctly
- Hysteresis: Mode doesn't flap rapidly (5-tick delay)
- Metrics: Verify entropy/CV/temporal drive correct mode selection
Regression Tests
- Run existing 936+ test suite with different L8 modes
- Verify deterministic behavior (no variance across runs)
- Check performance matches DoE predictions
Validation Checklist
After implementation, verify:
- L2:
rolling_window_record_execution()only executes when L2 bit set - L3:
physics_metadata_apply_linear_decay()returns early when L3 bit clear - L5:
find_variance_inflection()only called when L5 bit set - L6:
infer_decay_slope_q48()only called when L6 bit set - L8: Mode transitions logged in debug output
- All existing tests pass with default mode (C0 or C12)
- DoE experiment can reproduce 128 configurations via L8 mode forcing
Impact Assessment
Lines of code changed: ~40-60 lines across 3 files Risk: Low (adding guards, not changing logic) Performance: Neutral to positive (skips work when disabled) Correctness: High confidence (L8 already validated via DoE)
Open Questions
-
Default mode: Should VM initialize with C0 (minimal) or C12 (DoE optimal)?
- Current:
ssm_l8_init(..., SSM_MODE_C0)invm.c:387 - Top 5%: C4, C7, C9, C11, C12
- Recommendation: Start C0, let L8 adapt
- Current:
-
Mode override: Should FORTH words exist to force L8 mode?
L8-MODE!to set mode manuallyL8-MODE@to query current mode- Useful for debugging/benchmarking
-
Metrics exposure: Should heartbeat CSV export include L8 mode?
- Currently exports: executions, heat, variance, slope
- Add:
l8_modecolumn (0-15 integer)
Success Criteria
Definition of Done:
- All 4 feedback loops check
ssm_configflags before executing - L8 mode selector controls L2/L3/L5/L6 runtime behavior
- Mode transitions visible in debug logs
- Existing test suite passes with default L8 mode
- Can reproduce DoE configurations by forcing L8 modes 0-15
Verification:
# Build with L8 control wired
make clean && make fastest
# Test mode C0 (all off) - should be fastest, least adaptive
./build/amd64/fastest/starforth --benchmark 1000
# Test mode C15 (all on) - should be slowest, most adaptive
# (requires word to set L8 mode or environment variable)
# Run full test suite
make test
Next Steps
Before coding:
- Review this plan with stakeholders
- Decide on L2 implementation strategy (Option A vs B)
- Choose default L8 mode (C0 vs C12)
During implementation:
- Follow order: L3 → L5 → L6 → L2
- Test each loop individually before moving to next
- Update comments to reflect runtime gating
After completion:
- Run full DoE validation (128 configs × 300 reps)
- Verify 0% algorithmic variance maintained
- Document L8 control API for future developers