Skip to content

ltm: exhaustive-mode loops never get runtime polarity reclassification (Rux/Bux unreachable) #679

Closed

Description

@bpowers

Problem

LoopPolarity::from_runtime_scores (src/simlin-engine/src/ltm/types.rs:254) implements the LTM papers' Rux/Bux/U runtime classification with the POLARITY_CONFIDENCE_THRESHOLD (0.99) confidence gate. But its only production caller is the discovery FoundLoop path (src/simlin-engine/src/ltm_finding.rs:2036). The other in-tree caller (src/simlin-engine/src/db/analysis.rs:3074) is inside a #[cfg(test)] test, not a production surface.

In exhaustive mode, model_detected_loops (src/simlin-engine/src/db/analysis.rs) reports the structural label with polarity_confidence hardcoded to 1.0 (every link signed) or 0.0 (any link unknown), and never applies a runtime override. The code comments confirm this is intentional today:

  • db/analysis.rs:808-814 -- "For the structural pipeline (model_detected_loops), this is 1.0 when every link ... 0.0 when any link is unknown."
  • db/analysis.rs:829-831 -- "The structural model_detected_loops pipeline never produces these variants -- it has no runtime data."

So MostlyReinforcing/MostlyBalancing (Rux/Bux) are structurally unreachable through the exhaustive-mode detected-loops surface, even though the runtime loop_score series exists post-simulation and from_runtime_scores would yield the honest Rux/Bux/U label with a real confidence value.

Consequence

A loop whose polarity genuinely changes sign during the simulation (e.g. a logistic-style single-flow formulation, or the yeast-alcohol model from the LTM papers) is reported through the exhaustive path either:

  • with a confidently-wrong steady R/B label (under the older, looser polarity rules), or
  • with a blanket Undetermined (after the tightened Mul/Div polarity rules in cc072973),

when the truthful answer post-simulation is Rux/Bux/U with a concrete confidence. The loop_score series that would resolve this is available; it just is never consulted on the exhaustive path. This is a labelling fidelity gap -- loop scores themselves are unaffected.

Why it matters

Correctness/honesty of the polarity label that downstream consumers (UI rendering, pysimlin, MCP analyze) present to modelers. A "mostly reinforcing, confidence 0.97" loop and a "reinforcing, confidence 1.0" loop are materially different stories about the model's behavior, and the engine has the data to tell them apart but doesn't on the exhaustive surface.

Components affected

  • src/simlin-engine/src/ltm/types.rs (LoopPolarity::from_runtime_scores)
  • src/simlin-engine/src/db/analysis.rs (model_detected_loops, DetectedLoop::polarity_confidence, DetectedLoopPolarity)
  • src/libsimlin/src/analysis.rs (the post-sim analyze path that has the Results in hand and already coalesces the variants -- see ltm: surface MostlyReinforcing/MostlyBalancing in JSON/TS/WASM layers #495)

Suggested approach

model_detected_loops itself is a pre-simulation salsa query (no runtime series), so the override does not belong there. After simulation, reclassify exhaustive-mode loops from their per-loop loop_score series via from_runtime_scores. The libsimlin analyze path already holds the simulation Results and the loop ids, so the reclassification (structural label + polarity_confidence -> runtime label + real confidence) belongs in that post-sim surface, mirroring what the discovery FoundLoop path already does in-engine. This also unblocks the FFI/TS plumbing tracked in #495 from ever seeing a non-trivial confidence on the exhaustive path.

Related

Context

Found during a design review on 2026-06-02. Severity: moderate (labels only; loop scores unaffected).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ltmLoops that Matter (LTM) analysis subsystem

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions