diff --git a/pkg/lem/config.go b/pkg/lem/config.go index 58f2c88..3a5528a 100644 --- a/pkg/lem/config.go +++ b/pkg/lem/config.go @@ -19,11 +19,13 @@ type AIConfig struct { // ScorerConfig controls quality gating. type ScorerConfig struct { - Engine string `yaml:"engine"` - MinScore float64 `yaml:"min_score"` - Delta bool `yaml:"delta"` - SycophancyEcho float64 `yaml:"sycophancy_echo"` - SycophancyUplift float64 `yaml:"sycophancy_uplift"` + Engine string `yaml:"engine"` + MinScore float64 `yaml:"min_score"` + Delta bool `yaml:"delta"` + SycophancyEcho float64 `yaml:"sycophancy_echo"` + SycophancyUplift float64 `yaml:"sycophancy_uplift"` + Attention bool `yaml:"attention"` // Enable attention scoring in distill + AttentionMinScore float64 `yaml:"attention_min_score"` // Minimum BO composite (0-100, 0 = no gate) } // GenerateConfig holds default inference parameters. diff --git a/pkg/lem/distill.go b/pkg/lem/distill.go index cac403d..d14ffbe 100644 --- a/pkg/lem/distill.go +++ b/pkg/lem/distill.go @@ -258,6 +258,24 @@ func RunDistill(args []string) { } } + // Optional attention scoring — costs an extra prefill per probe. + if best != nil && aiCfg.Scorer.Attention { + snap, attErr := backend.InspectAttention(ctx, probe.Prompt) + if attErr == nil { + attResult := AnalyseAttention(snap) + boScore := attResult.Composite() + fmt.Fprintf(os.Stderr, " BO: coherence=%.2f phase=%.2f cross=%.2f composite=%.1f\n", + attResult.MeanCoherence, attResult.PhaseLockScore, attResult.MeanCrossAlignment, boScore) + if aiCfg.Scorer.AttentionMinScore > 0 && boScore < aiCfg.Scorer.AttentionMinScore { + skipped++ + fmt.Fprintf(os.Stderr, " ✗ SKIP %s (BO composite %.1f < %.1f)\n", + probe.ID, boScore, aiCfg.Scorer.AttentionMinScore) + runtime.GC() + continue + } + } + } + // Quality gate. if best != nil && best.Grammar.Composite >= *minScore { // Duplicate filter: reject if grammar profile is too similar to an already-kept entry.