@@ -768,4 +768,379 @@ mod tests {
768768 ) ;
769769 assert ! ( signal2. decay_rate < 0.90 , "high crystallization should decrease decay: {}" , signal2. decay_rate) ;
770770 }
771+
772+ #[ test]
773+ fn test_multi_agent_awareness_convergence_no_a2a ( ) {
774+ // Core awareness convergence test — no A2A dependency.
775+ // Simulates the feedback loop that runs when rustynum GEMM backend
776+ // produces hybrid pipeline results and ladybug-rs applies them.
777+ //
778+ // 3 "agents" each produce feedback signals with different characteristics:
779+ // - Explorer: high crystallization (settled knowledge)
780+ // - Contrarian: high sign flips (polarity conflict)
781+ // - Noisy: mostly mantissa noise (no real signal)
782+ //
783+ // A learner's WideMetaView accumulates feedback from all agents.
784+ // After multiple rounds, the NARS truth values, hybrid weights,
785+ // and RL Q-values should reflect the combined awareness.
786+ let mut words = [ 0u64 ; 256 ] ;
787+
788+ // Initialize: unknown state (f=0.5, c=0.1)
789+ let init_f = 0.5f32 ;
790+ let init_c = 0.1f32 ;
791+ words[ 4 ] = init_f. to_bits ( ) as u64 | ( ( init_c. to_bits ( ) as u64 ) << 32 ) ;
792+
793+ // Round 1: Explorer — crystallized, Revision signal
794+ let explorer = build_feedback (
795+ 500 , 16384 , 500 , 8000 , 8000 , 7500 ,
796+ 2 , 10 , 100 , 1024 , 0.90 , 0.03 ,
797+ & [ 0.85 ; 32 ] , 0.95 ,
798+ ) ;
799+ assert_eq ! ( explorer. bf16_causal. inferred_op, NarsInferenceType :: Revision ) ;
800+ apply_feedback ( & mut words, & explorer, 0.2 , 1 ) ;
801+
802+ let freq_r1 = f32:: from_bits ( ( words[ 4 ] & 0xFFFF_FFFF ) as u32 ) ;
803+ assert ! ( freq_r1 > init_f, "Revision should increase freq: {} > {}" , freq_r1, init_f) ;
804+
805+ // Round 2: Contrarian — negation signal
806+ let contrarian = build_feedback (
807+ 5000 , 16384 , 5000 , 8000 , 8000 , 3000 ,
808+ 400 , 50 , 100 , 1024 , 0.10 , 0.45 ,
809+ & [ 0.3 ; 32 ] , 0.95 ,
810+ ) ;
811+ assert_eq ! ( contrarian. bf16_causal. inferred_op, NarsInferenceType :: Negation ) ;
812+ apply_feedback ( & mut words, & contrarian, 0.2 , 2 ) ;
813+
814+ let freq_r2 = f32:: from_bits ( ( words[ 4 ] & 0xFFFF_FFFF ) as u32 ) ;
815+ // Negation should moderate the frequency
816+ assert ! ( freq_r2 > 0.0 && freq_r2 <= 1.0 , "Freq should be valid: {}" , freq_r2) ;
817+
818+ // Round 3: Explorer again — reinforces crystallized view
819+ let explorer2 = build_feedback (
820+ 300 , 16384 , 300 , 8200 , 8100 , 7800 ,
821+ 1 , 5 , 80 , 1024 , 0.92 , 0.02 ,
822+ & [ 0.9 ; 32 ] , 0.94 ,
823+ ) ;
824+ apply_feedback ( & mut words, & explorer2, 0.2 , 3 ) ;
825+
826+ let freq_r3 = f32:: from_bits ( ( words[ 4 ] & 0xFFFF_FFFF ) as u32 ) ;
827+ let conf_r3 = f32:: from_bits ( ( ( words[ 4 ] >> 32 ) & 0xFFFF_FFFF ) as u32 ) ;
828+
829+ // After 3 rounds, confidence should have accumulated
830+ assert ! ( conf_r3 > init_c, "Confidence should grow: {} > {}" , conf_r3, init_c) ;
831+
832+ // Hybrid weights should have been written (W144-W159)
833+ let hw0 = f32:: from_bits ( ( words[ 144 ] & 0xFFFF_FFFF ) as u32 ) ;
834+ let hw1 = f32:: from_bits ( ( ( words[ 144 ] >> 32 ) & 0xFFFF_FFFF ) as u32 ) ;
835+ assert ! ( hw0 > 0.0 && hw1 > 0.0 , "Hybrid weights should be positive: w0={}, w1={}" , hw0, hw1) ;
836+
837+ // Evidence history should have 3 entries shifted through (W162)
838+ let ev0 = f32:: from_bits ( ( words[ 162 ] & 0xFFFF_FFFF ) as u32 ) ;
839+ assert ! ( ev0 > 0.0 , "Evidence history should have entries: {}" , ev0) ;
840+
841+ // RL Q-values: action 0 (Revision) should be positive, action 1 (Negation) should exist
842+ let q_revision = f32:: from_bits ( ( words[ 32 ] & 0xFFFF_FFFF ) as u32 ) ;
843+ let q_negation = f32:: from_bits ( ( ( words[ 32 ] >> 32 ) & 0xFFFF_FFFF ) as u32 ) ;
844+ assert ! ( q_revision != 0.0 || q_negation != 0.0 ,
845+ "RL Q-values should be updated: revision={}, negation={}" , q_revision, q_negation) ;
846+
847+ // Decay rate should have adapted: contrarian increased it, explorer decreased it
848+ let decay = f32:: from_bits ( ( ( words[ 160 ] >> 32 ) & 0xFFFF_FFFF ) as u32 ) ;
849+ assert ! (
850+ decay > 0.0 && decay < 1.0 ,
851+ "Decay rate should be valid: {}" ,
852+ decay
853+ ) ;
854+ }
855+
856+ // ====================================================================
857+ // Multi-agent A2A awareness feedback bridge (requires crewai feature)
858+ // ====================================================================
859+ //
860+ // Demonstrates the full loop:
861+ //
862+ // Agent A: recognition → build_feedback() → encode as A2A message
863+ // ↓ A2A channel (XOR superposition in BindSpace 0x0F)
864+ // Agent B: read channel → decode feedback → apply_feedback() → WideMetaView
865+ //
866+ // When compiled into a single binary with rustynum, the recognition
867+ // results come from the GEMM-backed hybrid pipeline. Here we simulate
868+ // the numbers that would flow from hybrid_pipeline() + extract_learning_signal().
869+
870+ #[ test]
871+ #[ cfg( feature = "crewai" ) ]
872+ fn test_a2a_feedback_bridge_two_agents ( ) {
873+ use crate :: storage:: bind_space:: BindSpace ;
874+ use crate :: orchestration:: a2a:: {
875+ A2AProtocol , A2AMessage , MessageKind , DeliveryStatus ,
876+ } ;
877+
878+ let mut space = BindSpace :: new ( ) ;
879+ let mut protocol = A2AProtocol :: new ( ) ;
880+
881+ // Agent A (slot 0x02): explorer agent — produces crystallized feedback
882+ // Agent B (slot 0x05): learner agent — receives and applies feedback
883+ let _channel = protocol. open_channel ( 0x02 , 0x05 ) ;
884+
885+ // === Agent A's recognition cycle ===
886+ //
887+ // These numbers would come from rustynum hybrid_pipeline():
888+ // - hamming_distance, total_bits from K2 exact
889+ // - conflict/energy/agreement from EnergyConflict
890+ // - sign_flips etc. from BF16 structural diff
891+ // - crystallized/tension from extract_learning_signal()
892+ let agent_a_feedback = build_feedback (
893+ 800 , // hamming_distance (close match)
894+ 16384 , // total_bits (SKU-16K)
895+ 800 , // conflict
896+ 8100 , // energy_a
897+ 8050 , // energy_b
898+ 7250 , // agreement (high — good match)
899+ 3 , // sign_flips (few)
900+ 15 , // exponent_bits_changed
901+ 120 , // mantissa_bits_changed
902+ 1024 , // total_bf16_dims
903+ 0.85 , // crystallized_ratio (high — settled)
904+ 0.04 , // tension_ratio (low)
905+ & [ 0.8 ; 32 ] , // attention_weights from awareness
906+ 0.95 , // current_decay
907+ ) ;
908+
909+ // Verify Agent A's feedback is a revision signal (crystallized)
910+ assert_eq ! (
911+ agent_a_feedback. bf16_causal. inferred_op,
912+ NarsInferenceType :: Revision ,
913+ "High crystallization should trigger Revision"
914+ ) ;
915+
916+ // === Agent A sends feedback through A2A channel ===
917+ //
918+ // In production, the feedback signal's hybrid_weights would be
919+ // encoded into the message payload or fingerprint. Here we use
920+ // the Knowledge message kind to carry awareness.
921+ let feedback_msg = A2AMessage {
922+ id : "feedback-001" . to_string ( ) ,
923+ sender_slot : 0x02 ,
924+ receiver_slot : 0x05 ,
925+ kind : MessageKind :: Knowledge ,
926+ payload : format ! (
927+ "crystallized={:.2},tension={:.2},op={:?},decay={:.3}" ,
928+ agent_a_feedback. bf16_causal. crystallized_ratio,
929+ agent_a_feedback. bf16_causal. tension_ratio,
930+ agent_a_feedback. bf16_causal. inferred_op,
931+ agent_a_feedback. decay_rate,
932+ ) ,
933+ fingerprint : None ,
934+ timestamp : 1 ,
935+ status : DeliveryStatus :: Pending ,
936+ thinking_style_hint : Some ( "analytical" . to_string ( ) ) ,
937+ resonance_weight : 1.0 ,
938+ } ;
939+
940+ let status = protocol. send ( feedback_msg, & mut space) ;
941+ assert_eq ! ( status, DeliveryStatus :: Delivered ) ;
942+
943+ // Channel should show activity
944+ let resonance = protocol. field_resonance ( 0x02 , 0x05 ) ;
945+ assert ! (
946+ resonance > 0.0 ,
947+ "A2A channel should have resonance after feedback: {}" ,
948+ resonance
949+ ) ;
950+ assert_eq ! ( protocol. superposition_depth( 0x02 , 0x05 ) , 1 ) ;
951+
952+ // === Agent B reads the channel and applies feedback ===
953+ //
954+ // In production, Agent B would decode the message and reconstruct
955+ // the FeedbackSignal. Here we apply it directly to a WideMetaView buffer.
956+ let mut words_b = [ 0u64 ; 256 ] ;
957+
958+ // Initialize Agent B's NARS state (unknown: f=0.5, c=0.1)
959+ let init_freq = 0.5f32 ;
960+ let init_conf = 0.1f32 ;
961+ words_b[ 4 ] = init_freq. to_bits ( ) as u64 | ( ( init_conf. to_bits ( ) as u64 ) << 32 ) ;
962+
963+ // Apply Agent A's feedback to Agent B's metadata
964+ apply_feedback ( & mut words_b, & agent_a_feedback, 0.2 , 1 ) ;
965+
966+ // Verify Agent B's NARS state was updated
967+ let new_freq = f32:: from_bits ( ( words_b[ 4 ] & 0xFFFF_FFFF ) as u32 ) ;
968+ let new_conf = f32:: from_bits ( ( ( words_b[ 4 ] >> 32 ) & 0xFFFF_FFFF ) as u32 ) ;
969+ assert ! (
970+ new_freq > 0.5 ,
971+ "Revision from Agent A should increase frequency: {}" ,
972+ new_freq
973+ ) ;
974+ assert ! (
975+ new_conf > 0.0 && new_conf <= 1.0 ,
976+ "Confidence should be valid: {}" ,
977+ new_conf
978+ ) ;
979+
980+ // Verify hybrid weights were written to W144-W159
981+ let w0 = f32:: from_bits ( ( words_b[ 144 ] & 0xFFFF_FFFF ) as u32 ) ;
982+ assert ! (
983+ w0 > 0.0 ,
984+ "Hybrid weight W144 should be non-zero after feedback: {}" ,
985+ w0
986+ ) ;
987+
988+ // Verify RL Q-value was updated (action slot 0 = Revision)
989+ let q0 = f32:: from_bits ( ( words_b[ 32 ] & 0xFFFF_FFFF ) as u32 ) ;
990+ assert ! (
991+ q0 != 0.0 ,
992+ "Q-value for Revision action should be updated: {}" ,
993+ q0
994+ ) ;
995+ }
996+
997+ #[ test]
998+ #[ cfg( feature = "crewai" ) ]
999+ fn test_a2a_multi_round_feedback_convergence ( ) {
1000+ use crate :: storage:: bind_space:: BindSpace ;
1001+ use crate :: orchestration:: a2a:: {
1002+ A2AProtocol , A2AMessage , MessageKind , DeliveryStatus ,
1003+ } ;
1004+
1005+ let mut space = BindSpace :: new ( ) ;
1006+ let mut protocol = A2AProtocol :: new ( ) ;
1007+
1008+ // 3 agents: explorer(0), contrarian(1), learner(2)
1009+ // explorer → learner: crystallized feedback
1010+ // contrarian → learner: negation feedback
1011+ let _ch_explore = protocol. open_channel ( 0 , 2 ) ;
1012+ let _ch_contra = protocol. open_channel ( 1 , 2 ) ;
1013+
1014+ let mut learner_words = [ 0u64 ; 256 ] ;
1015+ let init_freq = 0.5f32 ;
1016+ let init_conf = 0.1f32 ;
1017+ learner_words[ 4 ] = init_freq. to_bits ( ) as u64 | ( ( init_conf. to_bits ( ) as u64 ) << 32 ) ;
1018+
1019+ // === Round 1: Explorer sends crystallized feedback ===
1020+ let explorer_feedback = build_feedback (
1021+ 500 , 16384 , 500 , 8000 , 8000 , 7500 ,
1022+ 2 , 10 , 100 , 1024 , 0.90 , 0.03 ,
1023+ & [ 0.85 ; 32 ] , 0.95 ,
1024+ ) ;
1025+ let msg_explore = A2AMessage {
1026+ id : "explore-r1" . to_string ( ) ,
1027+ sender_slot : 0 ,
1028+ receiver_slot : 2 ,
1029+ kind : MessageKind :: Knowledge ,
1030+ payload : "crystallized_feedback" . to_string ( ) ,
1031+ fingerprint : None ,
1032+ timestamp : 1 ,
1033+ status : DeliveryStatus :: Pending ,
1034+ thinking_style_hint : None ,
1035+ resonance_weight : 1.0 ,
1036+ } ;
1037+ protocol. send ( msg_explore, & mut space) ;
1038+ apply_feedback ( & mut learner_words, & explorer_feedback, 0.15 , 1 ) ;
1039+
1040+ let freq_after_r1 = f32:: from_bits ( ( learner_words[ 4 ] & 0xFFFF_FFFF ) as u32 ) ;
1041+
1042+ // === Round 2: Contrarian sends negation feedback ===
1043+ let contra_feedback = build_feedback (
1044+ 5000 , 16384 , 5000 , 8000 , 8000 , 3000 ,
1045+ 400 , 50 , 100 , 1024 , 0.10 , 0.45 ,
1046+ & [ 0.3 ; 32 ] , 0.95 ,
1047+ ) ;
1048+ let msg_contra = A2AMessage {
1049+ id : "contra-r2" . to_string ( ) ,
1050+ sender_slot : 1 ,
1051+ receiver_slot : 2 ,
1052+ kind : MessageKind :: Knowledge ,
1053+ payload : "negation_feedback" . to_string ( ) ,
1054+ fingerprint : None ,
1055+ timestamp : 2 ,
1056+ status : DeliveryStatus :: Pending ,
1057+ thinking_style_hint : None ,
1058+ resonance_weight : 1.0 ,
1059+ } ;
1060+ protocol. send ( msg_contra, & mut space) ;
1061+ apply_feedback ( & mut learner_words, & contra_feedback, 0.15 , 2 ) ;
1062+
1063+ let freq_after_r2 = f32:: from_bits ( ( learner_words[ 4 ] & 0xFFFF_FFFF ) as u32 ) ;
1064+
1065+ // === Round 3: Explorer sends another crystallized signal ===
1066+ let explorer_feedback_r3 = build_feedback (
1067+ 300 , 16384 , 300 , 8200 , 8100 , 7800 ,
1068+ 1 , 5 , 80 , 1024 , 0.92 , 0.02 ,
1069+ & [ 0.9 ; 32 ] , 0.94 ,
1070+ ) ;
1071+ let msg_explore_r3 = A2AMessage {
1072+ id : "explore-r3" . to_string ( ) ,
1073+ sender_slot : 0 ,
1074+ receiver_slot : 2 ,
1075+ kind : MessageKind :: Knowledge ,
1076+ payload : "crystallized_feedback_r3" . to_string ( ) ,
1077+ fingerprint : None ,
1078+ timestamp : 3 ,
1079+ status : DeliveryStatus :: Pending ,
1080+ thinking_style_hint : None ,
1081+ resonance_weight : 1.0 ,
1082+ } ;
1083+ protocol. send ( msg_explore_r3, & mut space) ;
1084+ apply_feedback ( & mut learner_words, & explorer_feedback_r3, 0.15 , 3 ) ;
1085+
1086+ let freq_after_r3 = f32:: from_bits ( ( learner_words[ 4 ] & 0xFFFF_FFFF ) as u32 ) ;
1087+
1088+ // === Verify convergence ===
1089+
1090+ // After contrarian negation, frequency should have been pushed down
1091+ assert ! (
1092+ freq_after_r2 < freq_after_r1 || freq_after_r2 < 0.8 ,
1093+ "Negation should reduce or moderate frequency: r1={:.3} r2={:.3}" ,
1094+ freq_after_r1, freq_after_r2
1095+ ) ;
1096+
1097+ // After 2nd explorer signal, frequency should recover
1098+ assert ! (
1099+ freq_after_r3 > 0.0 && freq_after_r3 <= 1.0 ,
1100+ "Final frequency should be valid: {}" ,
1101+ freq_after_r3
1102+ ) ;
1103+
1104+ // A2A channel awareness should accumulate
1105+ let explore_awareness = protocol. field_resonance ( 0 , 2 ) ;
1106+ let contra_awareness = protocol. field_resonance ( 1 , 2 ) ;
1107+ let total = protocol. total_awareness ( ) ;
1108+
1109+ assert ! (
1110+ explore_awareness > 0.0 ,
1111+ "Explorer channel should have resonance: {}" ,
1112+ explore_awareness
1113+ ) ;
1114+ assert ! (
1115+ contra_awareness > 0.0 ,
1116+ "Contrarian channel should have resonance: {}" ,
1117+ contra_awareness
1118+ ) ;
1119+ assert ! (
1120+ total > explore_awareness,
1121+ "Total awareness should exceed single channel: {}" ,
1122+ total
1123+ ) ;
1124+
1125+ // Explorer channel had 2 messages, should have higher superposition depth
1126+ assert_eq ! ( protocol. superposition_depth( 0 , 2 ) , 2 ) ;
1127+ assert_eq ! ( protocol. superposition_depth( 1 , 2 ) , 1 ) ;
1128+
1129+ // Hybrid weights should reflect mixed signals
1130+ let w0 = f32:: from_bits ( ( learner_words[ 144 ] & 0xFFFF_FFFF ) as u32 ) ;
1131+ let w1 = f32:: from_bits ( ( ( learner_words[ 144 ] >> 32 ) & 0xFFFF_FFFF ) as u32 ) ;
1132+ assert ! (
1133+ w0 > 0.0 && w1 > 0.0 ,
1134+ "Hybrid weights should be positive: w0={} w1={}" ,
1135+ w0, w1
1136+ ) ;
1137+
1138+ // Evidence history should have entries (W162)
1139+ let ev_slot0 = f32:: from_bits ( ( learner_words[ 162 ] & 0xFFFF_FFFF ) as u32 ) ;
1140+ assert ! (
1141+ ev_slot0 > 0.0 ,
1142+ "Evidence history should have entries: slot0={}" ,
1143+ ev_slot0
1144+ ) ;
1145+ }
7711146}
0 commit comments