legion-gaia 0.9.60 → 0.9.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fc298cf66a402057db523e07a53e4bde4a30a7b3e8090b819b8b1f64569c589
4
- data.tar.gz: 6226b64c0fc7ace2122f014784b21f074fae19ebfc214360b5288cfc7d9e80d0
3
+ metadata.gz: 12edcb61bebce4bbbd39edcbdb2db2281570ebea9fdf532b6b5ecf35a1d97fbb
4
+ data.tar.gz: aa4e1afe3bae0e4159d2d1975726f9dcafb216d4f72f63582f22089feae5a323
5
5
  SHA512:
6
- metadata.gz: d74b4bc1634a51f8759771f8c77711a58c92627487ca3c98e2af22c2244ab1c3efc63de5364656179d3232aa8d9ee9a0faa48f7a35d28dfa34aef21e493351bb
7
- data.tar.gz: 1d4f2872dc1b9e332160f34676dc1f5950dd6a13b2d72a050c4c65b693546e8743309ba34148cdc3d18cb103127279c5dd8d055832ad2db41e182dcb0adf17d5
6
+ metadata.gz: b404500f3c22a620c2ddb27957d1b163377cc0301059aaa5e7cf94d0b0880bf9377e9da9191a6680674a3ab5e3cce5f8c9d7019b0bd9874fd6180ebaf14cb1d4
7
+ data.tar.gz: 27d9e1ea9bbc959c18c7ddc0e101feddb569e2c8fd52f16106e39a1b96af510d4c1abd371b8ded229ae8b6e3d391b62a0d9e872d67a4b941a2762abb5981c397
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.61] - 2026-07-16
4
+ ### Added
5
+ - H4: `PartnerModel` — 7-slot working-memory competition (§12.4); only filter/transform/autonomous tier synapses compete, observe-tier excluded; unified ranking across synapses, traces, and preferences by `strength × (1 + emotional_intensity)`; `observe_mode_entries` transparency surface
6
+ - Wire `update_from_observation` on `PreferenceProfile` in `observe_interlocutor` (soft-guarded on lex-mesh)
7
+ - Correction detection: explicit negative feedback after applied behavioral response → correction trace via lex-agentic-memory
8
+ - Crystallization path: N corroborating corrections (default threshold 3) per (identity, domain) → `BehavioralSynapse.crystallize` with `origin: 'emergent'`
9
+
3
10
  ## [0.9.60] - 2026-07-16
4
11
  ### Added
5
12
  - H2: BehavioralSynapse store with vendored Confidence math (lifecycle: crystallize, lazy decay with intensity resistance, grade via record_outcome, pain at 3 consecutive failures → dampened + event emission)
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Gaia
5
+ module PartnerModel
6
+ # Working-memory slot competition (§12.4).
7
+ # Only filter/transform/autonomous tier synapses compete — observe-tier synapses are
8
+ # tracked separately via observe_mode_entries and never consume working-memory slots.
9
+ MAX_WORKING_MEMORY_SLOTS = 7
10
+
11
+ # Source-confidence defaults for preference candidates.
12
+ PREFERENCE_SOURCE_CONFIDENCE = {
13
+ explicit: 1.0,
14
+ preference_learning: 0.75,
15
+ llm_inference: 0.65,
16
+ observation: 0.55
17
+ }.freeze
18
+
19
+ module_function
20
+
21
+ # Build the working-memory slot array for this identity.
22
+ #
23
+ # @param identity [String, Symbol] the partner identity key
24
+ # @param synapses [Array, nil] BehavioralSynapse entries; fetched from store if nil
25
+ # @param traces [Array, nil] episodic/semantic trace hashes from lex-agentic-memory
26
+ # @param preferences [Array, nil] PreferenceProfile directives with :source and :content
27
+ # @return [Array<Hash>] at most MAX_WORKING_MEMORY_SLOTS slot hashes, sorted by score desc
28
+ def build(identity:, synapses: nil, traces: nil, preferences: nil)
29
+ identity_str = identity.to_s
30
+ candidates = []
31
+ candidates.concat(synapse_candidates(identity: identity_str, synapses: synapses))
32
+ candidates.concat(trace_candidates(traces))
33
+ candidates.concat(preference_candidates(preferences))
34
+
35
+ max_slots = Legion::Gaia.settings&.dig(:partner_model, :max_slots) || MAX_WORKING_MEMORY_SLOTS
36
+
37
+ candidates
38
+ .sort_by { |c| -slot_score(c) }
39
+ .first(max_slots)
40
+ .map { |c| c.slice(:type, :domain, :content, :strength, :source_id, :autonomy_mode) }
41
+ end
42
+
43
+ # Returns observe-tier synapses for this identity (transparency surface — not in working memory).
44
+ #
45
+ # @param identity [String, Symbol]
46
+ # @return [Array<Hash>]
47
+ def observe_mode_entries(identity:)
48
+ identity_str = identity.to_s
49
+ all = Legion::Gaia::BehavioralSynapse.all_for(identity: identity_str)
50
+ all.select { |entry| autonomy_tier(entry[:confidence]) == :observe }
51
+ .map do |entry|
52
+ {
53
+ type: :synapse,
54
+ domain: entry[:domain],
55
+ content: entry[:directive],
56
+ strength: entry[:confidence],
57
+ source_id: entry[:id],
58
+ autonomy_mode: :observe
59
+ }
60
+ end
61
+ end
62
+
63
+ # --- private helpers ---
64
+
65
+ def synapse_candidates(identity:, synapses:)
66
+ entries = synapses || Legion::Gaia::BehavioralSynapse.all_for(identity: identity)
67
+ entries.filter_map do |entry|
68
+ tier = autonomy_tier(entry[:confidence])
69
+ next unless %i[filter transform autonomous].include?(tier)
70
+
71
+ {
72
+ type: :synapse,
73
+ domain: entry[:domain],
74
+ content: entry[:directive],
75
+ strength: entry[:confidence].to_f,
76
+ emotional_intensity: entry[:emotional_intensity].to_f,
77
+ source_id: entry[:id],
78
+ autonomy_mode: tier
79
+ }
80
+ end
81
+ end
82
+
83
+ def trace_candidates(traces)
84
+ return [] unless traces.is_a?(Array)
85
+
86
+ traces.map do |trace|
87
+ strength = (trace[:strength] || trace[:confidence]).to_f
88
+ intensity = (trace[:emotional_intensity] || 0.5).to_f
89
+ {
90
+ type: :trace,
91
+ domain: Array(trace[:domain_tags]).first.to_s,
92
+ content: trace[:content_payload],
93
+ strength: strength,
94
+ emotional_intensity: intensity,
95
+ source_id: trace[:trace_id],
96
+ autonomy_mode: nil
97
+ }
98
+ end
99
+ end
100
+
101
+ def preference_candidates(preferences)
102
+ return [] unless preferences.is_a?(Array)
103
+
104
+ preferences.map do |pref|
105
+ source_key = pref[:source].to_s.to_sym
106
+ strength = PREFERENCE_SOURCE_CONFIDENCE.fetch(source_key, 0.55)
107
+ {
108
+ type: :preference,
109
+ domain: pref[:domain].to_s,
110
+ content: pref[:content] || pref[:directive],
111
+ strength: strength,
112
+ emotional_intensity: 0.3,
113
+ source_id: pref[:id],
114
+ autonomy_mode: nil
115
+ }
116
+ end
117
+ end
118
+
119
+ def slot_score(candidate)
120
+ strength = candidate[:strength].to_f
121
+ intensity = candidate[:emotional_intensity].to_f
122
+ strength * (1.0 + intensity)
123
+ end
124
+
125
+ def autonomy_tier(confidence)
126
+ Legion::Gaia::BehavioralSynapse::Math.autonomy_mode(confidence.to_f)
127
+ end
128
+
129
+ module_function :synapse_candidates, :trace_candidates, :preference_candidates,
130
+ :slot_score, :autonomy_tier
131
+ end
132
+ end
133
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Gaia
5
- VERSION = '0.9.60'
5
+ VERSION = '0.9.61'
6
6
  end
7
7
  end
data/lib/legion/gaia.rb CHANGED
@@ -33,6 +33,7 @@ require 'legion/gaia/offline_handler'
33
33
  require 'legion/gaia/proactive_dispatcher'
34
34
  require 'legion/gaia/bond_registry'
35
35
  require 'legion/gaia/behavioral_synapse'
36
+ require 'legion/gaia/partner_model'
36
37
  require 'legion/gaia/tracker_persistence'
37
38
  require 'legion/gaia/router'
38
39
 
@@ -120,6 +121,7 @@ module Legion
120
121
  @last_valences = nil
121
122
  @last_response_at = nil
122
123
  @last_applied = nil
124
+ @correction_counts = nil
123
125
  @tick_history = nil
124
126
  @tick_count = nil
125
127
  @started_at = nil
@@ -401,6 +403,7 @@ module Legion
401
403
  @registry.discover
402
404
  boot_channels
403
405
  boot_agent_bridge
406
+ @correction_counts = Concurrent::Hash.new(0)
404
407
  register_behavioral_synapse_tracker
405
408
  hydrate_from_apollo_local
406
409
  register_provisional_partner_prior
@@ -583,11 +586,7 @@ module Legion
583
586
  bootstrap.record_observation
584
587
  end
585
588
 
586
- # The gate: only partner-level identities get deep learning
587
- if BondRegistry.partner?(identity_str)
588
- record_interaction_trace(observation)
589
- evaluate_calibration(observation)
590
- end
589
+ run_partner_deep_learning(identity_str, observation) if BondRegistry.partner?(identity_str)
591
590
  rescue StandardError => e
592
591
  handle_exception(e, level: :warn, operation: 'gaia.observe_interlocutor', identity: identity)
593
592
  end
@@ -707,6 +706,91 @@ module Legion
707
706
  handle_exception(e, level: :warn, operation: 'gaia.grade_behavioral_synapses')
708
707
  end
709
708
 
709
+ def run_partner_deep_learning(identity_str, observation)
710
+ record_interaction_trace(observation)
711
+ evaluate_calibration(observation)
712
+ update_preference_profile(identity_str, observation)
713
+ detect_and_record_correction(identity_str, observation)
714
+ end
715
+
716
+ def update_preference_profile(identity_str, observation)
717
+ return unless defined?(Legion::Extensions::Mesh::Helpers::PreferenceProfile)
718
+
719
+ Legion::Extensions::Mesh::Helpers::PreferenceProfile.update_from_observation(
720
+ owner_id: identity_str, signals: observation
721
+ )
722
+ rescue StandardError => e
723
+ handle_exception(e, level: :debug, operation: 'gaia.update_preference_profile', identity: identity_str)
724
+ end
725
+
726
+ def detect_and_record_correction(identity_str, observation)
727
+ return unless defined?(Legion::Extensions::Agentic::Social::Calibration::Runners::Calibration)
728
+ return unless @last_applied&.dig(:behavioral_synapse_ids)&.any?
729
+
730
+ ensure_calibration_runner
731
+ feedback_result = @calibration_runner.detect_explicit_feedback(content: observation[:content].to_s)
732
+ return unless feedback_result[:feedback] == :negative
733
+
734
+ record_correction_trace(identity: identity_str, applied: @last_applied, observation: observation)
735
+ rescue StandardError => e
736
+ handle_exception(e, level: :debug, operation: 'gaia.detect_and_record_correction', identity: identity_str)
737
+ end
738
+
739
+ def record_correction_trace(identity:, applied:, observation:)
740
+ return unless defined?(Legion::Extensions::Agentic::Memory::Trace::Runners::Traces)
741
+
742
+ runner = Object.new
743
+ runner.extend(Legion::Extensions::Agentic::Memory::Trace::Runners::Traces)
744
+ runner.store_trace(
745
+ type: :correction,
746
+ content_payload: {
747
+ correction_type: :explicit_negative_feedback,
748
+ applied_synapse_ids: Array(applied[:behavioral_synapse_ids]),
749
+ channel: observation[:channel],
750
+ content_type: observation[:content_type]
751
+ },
752
+ domain_tags: ['correction', "partner:#{identity}"],
753
+ origin: :direct_experience,
754
+ emotional_valence: -0.5,
755
+ emotional_intensity: 0.6,
756
+ confidence: 0.9
757
+ ).tap do |trace_result|
758
+ log.info("[gaia] correction trace identity=#{identity} " \
759
+ "trace=#{trace_result[:trace_id].to_s[0, 8]} " \
760
+ "synapse_ids=#{Array(applied[:behavioral_synapse_ids]).size}")
761
+ check_crystallization(identity: identity, applied: applied, trace_id: trace_result[:trace_id])
762
+ end
763
+ rescue StandardError => e
764
+ handle_exception(e, level: :debug, operation: 'gaia.record_correction_trace', identity: identity)
765
+ end
766
+
767
+ def check_crystallization(identity:, applied:, trace_id:)
768
+ domain = applied[:domain].to_s
769
+ return if domain.empty?
770
+
771
+ threshold = settings&.dig(:partner_model, :crystallize_threshold) || 3
772
+ @correction_counts ||= Concurrent::Hash.new(0)
773
+ key = "#{identity}:#{domain}"
774
+ @correction_counts[key] += 1
775
+ count = @correction_counts[key]
776
+
777
+ return unless count >= threshold
778
+
779
+ synapse_ids = Array(applied[:behavioral_synapse_ids])
780
+ BehavioralSynapse.crystallize(
781
+ identity: identity,
782
+ domain: domain,
783
+ directive: applied[:directive].to_s,
784
+ evidence_trace_ids: synapse_ids + [trace_id.to_s],
785
+ origin: 'emergent'
786
+ )
787
+ @correction_counts[key] = 0
788
+ log.info("[gaia] crystallization triggered identity=#{identity} domain=#{domain} " \
789
+ "corrections=#{count} threshold=#{threshold}")
790
+ rescue StandardError => e
791
+ handle_exception(e, level: :debug, operation: 'gaia.check_crystallization', identity: identity)
792
+ end
793
+
710
794
  def fetch_imprint_multiplier
711
795
  # Soft-dep on lex-coldstart; returns settings default if unavailable
712
796
  return 1.0 unless defined?(Legion::Extensions::Coldstart) && Legion::Extensions::Coldstart.connected?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-gaia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.60
4
+ version: 0.9.61
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -443,6 +443,7 @@ files:
443
443
  - lib/legion/gaia/offline_handler.rb
444
444
  - lib/legion/gaia/output_frame.rb
445
445
  - lib/legion/gaia/output_router.rb
446
+ - lib/legion/gaia/partner_model.rb
446
447
  - lib/legion/gaia/phase_wiring.rb
447
448
  - lib/legion/gaia/proactive.rb
448
449
  - lib/legion/gaia/proactive_delivery.rb