Skip to content

Commit 4675690

Browse files
committed
feat: implement 3 cognitive primitives (tactics #23, #12, #21)
Tactic #23 AMP (Adaptive Meta-Prompting): GestaltState -> inference_biases(), confidence_modifier(), chain_depth_delta() Crystallizing prefers Deduction, Contested prefers Analogy/Abduction, Dissolving prefers Abduction, Epiphany prefers Induction. Tactic #12 TCA (Temporal Context Augmentation): TruthTrajectory -> temporal_monotonicity(), temporal_acceleration(), has_temporal_precedence(). Granger-style analysis: evidence events followed by confidence rises = causal temporal precedence. Tactic #21 SSR (Self-Skeptical Reasoning): SkepticismLevel (Trust/Cautious/Skeptical/Doubting) derived from trajectory dynamics. High confidence from few events = Skeptical. damping_factor() multiplies NARS confidence before gating. 5 new tests across the three tactics. https://claude.ai/code/session_0152b2NJYnjCJjvMAmgsTx3p
1 parent 391c533 commit 4675690

1 file changed

Lines changed: 333 additions & 2 deletions

File tree

src/spo/gestalt.rs

Lines changed: 333 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -758,12 +758,220 @@ impl AntialiasedSigma {
758758
}
759759

760760
// =============================================================================
761-
// PHASE 7: GESTALT ENGINE — Connects harvest pipeline to decision lifecycle
761+
// TACTIC #23 AMP: GestaltState → InferenceRuleKind bias
762762
// =============================================================================
763763

764-
use crate::nars::TruthValue;
764+
use crate::nars::{InferenceRuleKind, TruthValue};
765765
use super::spo_harvest::AccumulatedHarvest;
766766

767+
/// Per-rule bias vector derived from GestaltState.
768+
///
769+
/// Tactic #23 (Adaptive Meta-Prompting): the gestalt state of the evidence
770+
/// influences which NARS inference rules are preferred.
771+
///
772+
/// ```text
773+
/// Crystallizing → prefer Deduction (commit forward chains)
774+
/// Contested → prefer Analogy + Comparison (seek alternatives)
775+
/// Dissolving → prefer Abduction (find alternative explanations)
776+
/// Epiphany → prefer Induction (generalize from new evidence)
777+
/// ```
778+
impl GestaltState {
779+
/// Produce a 5-element bias vector `[(rule, weight); 5]` for NARS inference.
780+
///
781+
/// Positive weight = prefer this rule, negative = suppress.
782+
/// Magnitudes are normalized to [-1.0, +1.0].
783+
pub fn inference_biases(&self) -> [(InferenceRuleKind, f32); 5] {
784+
match self {
785+
GestaltState::Crystallizing => [
786+
(InferenceRuleKind::Deduction, 0.8), // commit forward chains
787+
(InferenceRuleKind::Revision, 0.5), // consolidate evidence
788+
(InferenceRuleKind::Induction, 0.0), // neutral
789+
(InferenceRuleKind::Abduction, -0.3), // suppress speculation
790+
(InferenceRuleKind::Analogy, -0.2), // suppress lateral moves
791+
],
792+
GestaltState::Contested => [
793+
(InferenceRuleKind::Analogy, 0.7), // seek parallel structures
794+
(InferenceRuleKind::Abduction, 0.5), // seek alternative causes
795+
(InferenceRuleKind::Revision, 0.3), // combine conflicting evidence
796+
(InferenceRuleKind::Deduction, -0.4), // don't commit yet
797+
(InferenceRuleKind::Induction, 0.0), // neutral
798+
],
799+
GestaltState::Dissolving => [
800+
(InferenceRuleKind::Abduction, 0.8), // find what went wrong
801+
(InferenceRuleKind::Analogy, 0.4), // find similar patterns
802+
(InferenceRuleKind::Induction, 0.2), // re-generalize
803+
(InferenceRuleKind::Deduction, -0.6), // don't chain from dissolving base
804+
(InferenceRuleKind::Revision, -0.3), // old evidence is suspect
805+
],
806+
GestaltState::Epiphany => [
807+
(InferenceRuleKind::Induction, 0.8), // generalize from new evidence
808+
(InferenceRuleKind::Revision, 0.6), // integrate with existing
809+
(InferenceRuleKind::Analogy, 0.4), // find analogies
810+
(InferenceRuleKind::Deduction, 0.0), // neutral
811+
(InferenceRuleKind::Abduction, -0.2), // new evidence, not explaining failure
812+
],
813+
}
814+
}
815+
816+
/// Confidence modifier: how much to trust current evidence.
817+
///
818+
/// Crystallizing = boost, Contested = dampen, Dissolving = heavily dampen.
819+
pub fn confidence_modifier(&self) -> f32 {
820+
match self {
821+
GestaltState::Crystallizing => 1.2,
822+
GestaltState::Contested => 0.8,
823+
GestaltState::Dissolving => 0.5,
824+
GestaltState::Epiphany => 1.0,
825+
}
826+
}
827+
828+
/// Chain depth delta: how deep inference chains should go.
829+
///
830+
/// Crystallizing = shallow (already converging), Contested = deep (explore).
831+
pub fn chain_depth_delta(&self) -> i8 {
832+
match self {
833+
GestaltState::Crystallizing => -1,
834+
GestaltState::Contested => 2,
835+
GestaltState::Dissolving => 3,
836+
GestaltState::Epiphany => 1,
837+
}
838+
}
839+
}
840+
841+
// =============================================================================
842+
// TACTIC #12 TCA: Temporal ordering on TruthTrajectory
843+
// =============================================================================
844+
845+
impl TruthTrajectory {
846+
/// Granger-style temporal ordering: does confidence monotonically increase?
847+
///
848+
/// Returns the fraction of consecutive events where confidence increased.
849+
/// 1.0 = perfectly monotone (strong causal signal).
850+
/// 0.0 = no increase at all (no causal direction).
851+
pub fn temporal_monotonicity(&self) -> f32 {
852+
if self.events.len() < 2 {
853+
return 0.0;
854+
}
855+
let increases = self
856+
.events
857+
.windows(2)
858+
.filter(|w| w[1].nars_confidence > w[0].nars_confidence)
859+
.count();
860+
increases as f32 / (self.events.len() - 1) as f32
861+
}
862+
863+
/// Temporal acceleration: is confidence gain speeding up or slowing down?
864+
///
865+
/// Positive = accelerating (evidence snowball), negative = decelerating.
866+
/// Uses finite difference of consecutive confidence deltas.
867+
pub fn temporal_acceleration(&self) -> f32 {
868+
if self.events.len() < 3 {
869+
return 0.0;
870+
}
871+
let n = self.events.len();
872+
let delta_recent = self.events[n - 1].nars_confidence - self.events[n - 2].nars_confidence;
873+
let delta_prev = self.events[n - 2].nars_confidence - self.events[n - 3].nars_confidence;
874+
delta_recent - delta_prev
875+
}
876+
877+
/// Whether the trajectory shows causal temporal precedence:
878+
/// evidence consistently arrives before confidence rises (Granger criterion).
879+
///
880+
/// Returns true if at least 60% of evidence events are followed by
881+
/// confidence increases within the next 2 events.
882+
pub fn has_temporal_precedence(&self) -> bool {
883+
if self.events.len() < 3 {
884+
return false;
885+
}
886+
let mut precedence_count = 0;
887+
let mut evidence_count = 0;
888+
889+
for i in 0..self.events.len().saturating_sub(2) {
890+
if matches!(
891+
self.events[i].event_type,
892+
EvidenceEventType::MatchesAdded(_)
893+
) {
894+
evidence_count += 1;
895+
// Check if confidence rises in next 1-2 events
896+
let base_conf = self.events[i].nars_confidence;
897+
let rises = (i + 1..=(i + 2).min(self.events.len() - 1))
898+
.any(|j| self.events[j].nars_confidence > base_conf);
899+
if rises {
900+
precedence_count += 1;
901+
}
902+
}
903+
}
904+
905+
evidence_count > 0 && (precedence_count as f32 / evidence_count as f32) >= 0.6
906+
}
907+
}
908+
909+
// =============================================================================
910+
// TACTIC #21 SSR: Skepticism Schedule from confidence trend
911+
// =============================================================================
912+
913+
/// Skepticism level derived from TruthTrajectory dynamics.
914+
///
915+
/// Tactic #21 (Self-Skeptical Reasoning): when confidence rises too fast,
916+
/// inject skepticism to prevent premature crystallization.
917+
#[derive(Debug, Clone, Copy, PartialEq)]
918+
pub enum SkepticismLevel {
919+
/// Trust the evidence: confidence is rising steadily from many events.
920+
Trust,
921+
/// Mild caution: confidence is rising but from few events.
922+
Cautious,
923+
/// Active skepticism: confidence jumped suddenly (possible artifact).
924+
Skeptical,
925+
/// Strong doubt: confidence is oscillating or contradictory.
926+
Doubting,
927+
}
928+
929+
impl SkepticismLevel {
930+
/// Derive skepticism from a truth trajectory.
931+
pub fn from_trajectory(trajectory: &TruthTrajectory) -> Self {
932+
let n = trajectory.events.len();
933+
if n < 2 {
934+
return SkepticismLevel::Cautious; // not enough data
935+
}
936+
937+
let monotonicity = trajectory.temporal_monotonicity();
938+
let acceleration = trajectory.temporal_acceleration();
939+
let (_, confidence) = trajectory.current_truth();
940+
941+
// High confidence from very few events → skeptical
942+
if confidence > 0.9 && n < 5 {
943+
return SkepticismLevel::Skeptical;
944+
}
945+
946+
// Oscillating confidence (low monotonicity) → doubting
947+
if monotonicity < 0.3 && n > 4 {
948+
return SkepticismLevel::Doubting;
949+
}
950+
951+
// Sharp acceleration + high confidence → skeptical (too fast)
952+
if acceleration > 0.1 && confidence > 0.8 {
953+
return SkepticismLevel::Skeptical;
954+
}
955+
956+
// Steady monotone rise from many events → trust
957+
if monotonicity > 0.7 && n > 5 {
958+
return SkepticismLevel::Trust;
959+
}
960+
961+
SkepticismLevel::Cautious
962+
}
963+
964+
/// Confidence damping factor: multiply with NARS confidence before gating.
965+
pub fn damping_factor(&self) -> f32 {
966+
match self {
967+
SkepticismLevel::Trust => 1.0,
968+
SkepticismLevel::Cautious => 0.9,
969+
SkepticismLevel::Skeptical => 0.7,
970+
SkepticismLevel::Doubting => 0.5,
971+
}
972+
}
973+
}
974+
767975
/// The engine that bridges AccumulatedHarvest → BundlingProposal → TruthTrajectory.
768976
///
769977
/// Phase 7 integration: takes evidence from the harvest pipeline (Phases 2-6)
@@ -1246,6 +1454,129 @@ mod tests {
12461454
assert!(engine.trajectories[idx].proposal.is_committed());
12471455
}
12481456

1457+
// =========================================================================
1458+
// Tactic #23 AMP: inference biases from gestalt state
1459+
// =========================================================================
1460+
1461+
#[test]
1462+
fn test_gestalt_inference_biases() {
1463+
let biases = GestaltState::Crystallizing.inference_biases();
1464+
// Crystallizing should prefer Deduction
1465+
let deduction_bias = biases.iter().find(|(r, _)| *r == InferenceRuleKind::Deduction).unwrap().1;
1466+
let abduction_bias = biases.iter().find(|(r, _)| *r == InferenceRuleKind::Abduction).unwrap().1;
1467+
assert!(deduction_bias > 0.0);
1468+
assert!(abduction_bias < 0.0);
1469+
1470+
let biases = GestaltState::Dissolving.inference_biases();
1471+
// Dissolving should prefer Abduction
1472+
let abduction_bias = biases.iter().find(|(r, _)| *r == InferenceRuleKind::Abduction).unwrap().1;
1473+
let deduction_bias = biases.iter().find(|(r, _)| *r == InferenceRuleKind::Deduction).unwrap().1;
1474+
assert!(abduction_bias > 0.0);
1475+
assert!(deduction_bias < 0.0);
1476+
}
1477+
1478+
#[test]
1479+
fn test_gestalt_confidence_modifier() {
1480+
assert!(GestaltState::Crystallizing.confidence_modifier() > 1.0);
1481+
assert!(GestaltState::Contested.confidence_modifier() < 1.0);
1482+
assert!(GestaltState::Dissolving.confidence_modifier() < GestaltState::Contested.confidence_modifier());
1483+
}
1484+
1485+
// =========================================================================
1486+
// Tactic #12 TCA: temporal ordering tests
1487+
// =========================================================================
1488+
1489+
#[test]
1490+
fn test_temporal_monotonicity() {
1491+
let proposal = BundlingProposal::new_tentative(
1492+
"a".to_string(), "b".to_string(),
1493+
BundlingType::PredicateInversion,
1494+
200, 7500, 300, 0.5, 0.5,
1495+
rustynum_core::SignificanceLevel::Evidence, 10,
1496+
);
1497+
let mut trajectory = TruthTrajectory::new(proposal);
1498+
1499+
// Add monotonically increasing confidence events
1500+
for i in 1..=5 {
1501+
trajectory.record_event(EvidenceEvent {
1502+
timestamp_ms: i * 1000,
1503+
event_type: EvidenceEventType::MatchesAdded(10),
1504+
nars_frequency: 0.5 + i as f32 * 0.08,
1505+
nars_confidence: 0.5 + i as f32 * 0.08,
1506+
significance: rustynum_core::SignificanceLevel::Evidence,
1507+
evidence_count: i as u32 * 10,
1508+
gestalt: GestaltState::Crystallizing,
1509+
});
1510+
}
1511+
1512+
// 5 increases out of 5 windows = 1.0 (initial event confidence was 0.5)
1513+
let mono = trajectory.temporal_monotonicity();
1514+
assert!(mono > 0.8, "Expected high monotonicity, got {}", mono);
1515+
assert!(trajectory.has_temporal_precedence());
1516+
}
1517+
1518+
// =========================================================================
1519+
// Tactic #21 SSR: skepticism schedule tests
1520+
// =========================================================================
1521+
1522+
#[test]
1523+
fn test_skepticism_too_fast() {
1524+
let proposal = BundlingProposal::new_tentative(
1525+
"a".to_string(), "b".to_string(),
1526+
BundlingType::PredicateInversion,
1527+
200, 7500, 300, 0.95, 0.95,
1528+
rustynum_core::SignificanceLevel::Discovery, 3,
1529+
);
1530+
let mut trajectory = TruthTrajectory::new(proposal);
1531+
1532+
// Only 2 events but very high confidence → skeptical
1533+
trajectory.record_event(EvidenceEvent {
1534+
timestamp_ms: 1000,
1535+
event_type: EvidenceEventType::MatchesAdded(10),
1536+
nars_frequency: 0.96,
1537+
nars_confidence: 0.96,
1538+
significance: rustynum_core::SignificanceLevel::Discovery,
1539+
evidence_count: 13,
1540+
gestalt: GestaltState::Crystallizing,
1541+
});
1542+
1543+
let skepticism = SkepticismLevel::from_trajectory(&trajectory);
1544+
assert_eq!(skepticism, SkepticismLevel::Skeptical);
1545+
assert!(skepticism.damping_factor() < 1.0);
1546+
}
1547+
1548+
#[test]
1549+
fn test_skepticism_steady_rise() {
1550+
let proposal = BundlingProposal::new_tentative(
1551+
"a".to_string(), "b".to_string(),
1552+
BundlingType::PredicateInversion,
1553+
200, 7500, 300, 0.4, 0.4,
1554+
rustynum_core::SignificanceLevel::Hint, 5,
1555+
);
1556+
let mut trajectory = TruthTrajectory::new(proposal);
1557+
1558+
// Many events with steady monotone rise → trust
1559+
for i in 1..=8 {
1560+
trajectory.record_event(EvidenceEvent {
1561+
timestamp_ms: i * 1000,
1562+
event_type: EvidenceEventType::MatchesAdded(10),
1563+
nars_frequency: 0.4 + i as f32 * 0.06,
1564+
nars_confidence: 0.4 + i as f32 * 0.06,
1565+
significance: rustynum_core::SignificanceLevel::Evidence,
1566+
evidence_count: (5 + i * 10) as u32,
1567+
gestalt: GestaltState::Crystallizing,
1568+
});
1569+
}
1570+
1571+
let skepticism = SkepticismLevel::from_trajectory(&trajectory);
1572+
assert_eq!(skepticism, SkepticismLevel::Trust);
1573+
assert!((skepticism.damping_factor() - 1.0).abs() < f32::EPSILON);
1574+
}
1575+
1576+
// =========================================================================
1577+
// Phase 7: GestaltEngine tests
1578+
// =========================================================================
1579+
12491580
#[test]
12501581
fn test_engine_gestalt_summary() {
12511582
let gate = rustynum_core::SigmaGate::sku_16k();

0 commit comments

Comments
 (0)