legion-gaia 0.9.66 → 0.9.67
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 +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/legion/gaia/anticipation.rb +238 -0
- data/lib/legion/gaia/gut.rb +259 -0
- data/lib/legion/gaia/settings.rb +17 -1
- data/lib/legion/gaia/version.rb +1 -1
- data/lib/legion/gaia.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a478fc13f85356c56fd876e2a2fb8b875bdcb5d1f4ad74ffb109af0c29281d96
|
|
4
|
+
data.tar.gz: 5e8fd3ac446ff77d56774c53d26a1ff49f1e7fedb58614091fa79bb297fc32b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c643cd9e9b6c68990543afb913dae64e4768c9e2d869d34ab5d9ac46a00e02f1d9c2562011d10ca6708ca5e047bfbda9fb5b26efd691f2f95e3aff44b22dd49e
|
|
7
|
+
data.tar.gz: aaff63a4112a475a5ada9c77907bafaa1f732e0a113f8720a2115b8be153a31f70c8b00704dcc52ff0d293cbabee88ed727b00d014cd306a9f1ccc43764c61b6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.67] - 2026-07-24
|
|
4
|
+
### Added
|
|
5
|
+
- H5: `Legion::Gaia::Gut.check(identity:, draft_stats:)` — fast per-response conflict detection (v1: correction veto only). Compares draft stats against high-strength correction traces and transform-tier behavioral synapses. No LLM calls. Completes within 50ms latency budget (scalar setting); degrades to no-op if exceeded. Soft-guarded on lex-agentic-memory. The gut itself is a behavioral synapse (`domain: 'gut'`); new partners start in observe mode (emergent confidence 0.3 → observe tier).
|
|
6
|
+
- H6: `Legion::Gaia::Anticipation.build(identity:, context:)` — builds anticipation context from actionable pending predictions (confidence >= threshold). Pre-stages RAG entries capped at 25% of total. Soft-guarded on lex-agentic-inference.
|
|
7
|
+
- H6: `Legion::Gaia::Anticipation.resolve(identity:, prediction_id:, actual:)` — grades a pending prediction as `:correct`, `:incorrect`, or `:partial` against the partner's actual input. Entropy guard: no resolution under high identity entropy.
|
|
8
|
+
- H6: `Legion::Gaia::Anticipation.deferred?(identity:, anticipation:)` — §12.8 emotional modulation: negative-valence anticipations defer when partner affect baseline is low. Soft-guarded on lex-agentic-affect.
|
|
9
|
+
- Settings defaults: `gut.latency_budget_ms` (50ms), `gut.correction_min_strength` (0.6), `gut.correction_trace_limit` (20), `anticipation.actionable_threshold` (0.65)
|
|
10
|
+
|
|
3
11
|
## [0.9.66] - 2026-07-24
|
|
4
12
|
### Added
|
|
5
13
|
- `Gaia.observe_from_pipeline(identity:, caller:, exchange_id:)` — pipeline observation for API clients; reinforces bond, triggers coldstart, registers duckling partner on first human request
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require 'legion/logging/helper'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module Gaia
|
|
8
|
+
# H6 — prediction pre-staging with emotional modulation (§12.8).
|
|
9
|
+
#
|
|
10
|
+
# Reads pending predictions from lex-agentic-inference (if available).
|
|
11
|
+
# Actionable predictions (confidence >= threshold) trigger pre-staged RAG retrieval,
|
|
12
|
+
# capped at 25% of total entries.
|
|
13
|
+
#
|
|
14
|
+
# Emotional modulation: negative-valence anticipations defer when the partner's affect
|
|
15
|
+
# baseline is low (soft-guarded on lex-agentic-affect).
|
|
16
|
+
#
|
|
17
|
+
# Entropy guard: no resolution under high identity entropy (consistent with H2).
|
|
18
|
+
# Soft-guarded throughout: returns nil when dependencies are absent.
|
|
19
|
+
module Anticipation
|
|
20
|
+
extend Legion::Logging::Helper
|
|
21
|
+
|
|
22
|
+
# Threshold below which the partner's affect baseline is considered "low".
|
|
23
|
+
LOW_AFFECT_BASELINE = 0.3
|
|
24
|
+
|
|
25
|
+
# Cap on pre-staged entries as a fraction of total RAG entries.
|
|
26
|
+
PRESTAGE_CAP_RATIO = 0.25
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
# Build anticipation context for advisory.
|
|
31
|
+
#
|
|
32
|
+
# @param identity [String] partner identity
|
|
33
|
+
# @param context [Hash] current request context, e.g. { rag_entry_count:, entropy: }
|
|
34
|
+
# @return [Hash, nil]
|
|
35
|
+
# { prediction_id:, content:, confidence:, pre_staged: [...] }
|
|
36
|
+
# nil when lex-agentic-inference not loaded or no actionable predictions
|
|
37
|
+
def build(identity:, context:)
|
|
38
|
+
return nil unless inference_available?
|
|
39
|
+
|
|
40
|
+
runner = inference_runner
|
|
41
|
+
result = runner.pending_predictions(identity: identity.to_s)
|
|
42
|
+
predictions = Array(result[:predictions])
|
|
43
|
+
return nil if predictions.empty?
|
|
44
|
+
|
|
45
|
+
actionable = predictions.select { |p| p[:confidence].to_f >= actionable_threshold }
|
|
46
|
+
return nil if actionable.empty?
|
|
47
|
+
|
|
48
|
+
# Pick the highest-confidence actionable prediction
|
|
49
|
+
best = actionable.max_by { |p| p[:confidence].to_f }
|
|
50
|
+
|
|
51
|
+
# Emotional modulation: defer negative-valence anticipation when affect baseline is low
|
|
52
|
+
if deferred?(identity: identity, anticipation: best)
|
|
53
|
+
log.info("[anticipation] deferred identity=#{identity} prediction_id=#{best[:prediction_id].to_s[0, 8]} " \
|
|
54
|
+
'reason=emotional_state')
|
|
55
|
+
return nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
pre_staged = build_prestaged(context: context, prediction: best)
|
|
59
|
+
|
|
60
|
+
log.info("[anticipation] built identity=#{identity} prediction_id=#{best[:prediction_id].to_s[0, 8]} " \
|
|
61
|
+
"confidence=#{best[:confidence].to_f.round(3)} pre_staged=#{pre_staged.size}")
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
prediction_id: best[:prediction_id],
|
|
65
|
+
content: best[:description] || best[:context].to_s,
|
|
66
|
+
confidence: best[:confidence].to_f,
|
|
67
|
+
pre_staged: pre_staged
|
|
68
|
+
}
|
|
69
|
+
rescue StandardError => e
|
|
70
|
+
handle_exception(e, level: :warn, operation: 'gaia.anticipation.build', identity: identity)
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Resolve a pending anticipation against the partner's actual next input.
|
|
75
|
+
#
|
|
76
|
+
# Grades the prediction as :correct, :incorrect, or :partial and records the outcome.
|
|
77
|
+
# Skipped under high identity entropy.
|
|
78
|
+
#
|
|
79
|
+
# @param identity [String] partner identity
|
|
80
|
+
# @param prediction_id [String] UUID of the prediction to resolve
|
|
81
|
+
# @param actual [String] the partner's actual input
|
|
82
|
+
# @return [Hash, nil]
|
|
83
|
+
# { resolved: true/false, outcome:, prediction_id: }
|
|
84
|
+
# nil when dependencies unavailable
|
|
85
|
+
def resolve(identity:, prediction_id:, actual:)
|
|
86
|
+
return nil unless inference_available?
|
|
87
|
+
|
|
88
|
+
identity_str = identity.to_s
|
|
89
|
+
|
|
90
|
+
if high_entropy?(identity: identity_str)
|
|
91
|
+
log.info("[anticipation] resolve skipped identity=#{identity_str} reason=high_entropy")
|
|
92
|
+
return { resolved: false, reason: :high_entropy, prediction_id: prediction_id }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
outcome = grade_prediction(prediction_id: prediction_id, actual: actual, identity: identity_str)
|
|
96
|
+
runner = inference_runner
|
|
97
|
+
result = runner.resolve_prediction(prediction_id: prediction_id, outcome: outcome,
|
|
98
|
+
actual: actual, identity: identity_str)
|
|
99
|
+
|
|
100
|
+
log.info("[anticipation] resolved identity=#{identity_str} prediction_id=#{prediction_id.to_s[0, 8]} " \
|
|
101
|
+
"outcome=#{outcome}")
|
|
102
|
+
|
|
103
|
+
result
|
|
104
|
+
rescue StandardError => e
|
|
105
|
+
handle_exception(e, level: :warn, operation: 'gaia.anticipation.resolve',
|
|
106
|
+
identity: identity, prediction_id: prediction_id)
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Check if an anticipation should be deferred due to emotional state.
|
|
111
|
+
#
|
|
112
|
+
# Deferred when the anticipation has negative emotional context AND the partner's
|
|
113
|
+
# current affect baseline is low (§12.8 emotional modulation).
|
|
114
|
+
#
|
|
115
|
+
# @param identity [String] partner identity
|
|
116
|
+
# @param anticipation [Hash] prediction entry with optional :emotional_context
|
|
117
|
+
# @return [Boolean]
|
|
118
|
+
def deferred?(identity:, anticipation:)
|
|
119
|
+
return false unless anticipation.is_a?(Hash)
|
|
120
|
+
|
|
121
|
+
anticipation_valence = extract_valence(anticipation)
|
|
122
|
+
return false unless anticipation_valence < 0.0
|
|
123
|
+
|
|
124
|
+
affect_baseline_low?(identity: identity.to_s)
|
|
125
|
+
rescue StandardError => e
|
|
126
|
+
handle_exception(e, level: :debug, operation: 'gaia.anticipation.deferred', identity: identity)
|
|
127
|
+
false
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def inference_available?
|
|
131
|
+
defined?(Legion::Extensions::Agentic::Inference::Prediction::Runners::Prediction) == 'constant'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def inference_runner
|
|
135
|
+
runner = Object.new
|
|
136
|
+
runner.extend(Legion::Extensions::Agentic::Inference::Prediction::Runners::Prediction)
|
|
137
|
+
runner
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def actionable_threshold
|
|
141
|
+
settings = Legion::Gaia.settings
|
|
142
|
+
settings&.dig(:anticipation, :actionable_threshold) ||
|
|
143
|
+
(defined?(Legion::Extensions::Agentic::Inference::Prediction::Helpers::Modes::PREDICTION_CONFIDENCE_MIN) &&
|
|
144
|
+
Legion::Extensions::Agentic::Inference::Prediction::Helpers::Modes::PREDICTION_CONFIDENCE_MIN) ||
|
|
145
|
+
0.65
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Builds pre-staged RAG entries capped at 25% of total context entries.
|
|
149
|
+
def build_prestaged(context:, prediction:)
|
|
150
|
+
return [] unless context.is_a?(Hash)
|
|
151
|
+
|
|
152
|
+
rag_count = context[:rag_entry_count].to_i
|
|
153
|
+
return [] if rag_count.zero?
|
|
154
|
+
|
|
155
|
+
cap = [(rag_count * PRESTAGE_CAP_RATIO).ceil, 1].max
|
|
156
|
+
prediction_context = prediction[:context]
|
|
157
|
+
return [] unless prediction_context.is_a?(Hash) && prediction_context[:pre_stage_entries].is_a?(Array)
|
|
158
|
+
|
|
159
|
+
prediction_context[:pre_stage_entries].first(cap)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Grade a prediction as :correct, :incorrect, or :partial.
|
|
163
|
+
def grade_prediction(prediction_id:, actual:, identity:)
|
|
164
|
+
return :incorrect unless inference_available?
|
|
165
|
+
|
|
166
|
+
runner = inference_runner
|
|
167
|
+
pred_result = runner.get_prediction(prediction_id: prediction_id, identity: identity)
|
|
168
|
+
return :incorrect unless pred_result[:found]
|
|
169
|
+
|
|
170
|
+
prediction = pred_result[:prediction]
|
|
171
|
+
predicted = prediction[:description].to_s.downcase.strip
|
|
172
|
+
actual_str = actual.to_s.downcase.strip
|
|
173
|
+
|
|
174
|
+
return :incorrect if predicted.empty? || actual_str.empty?
|
|
175
|
+
|
|
176
|
+
if actual_str.include?(predicted) || predicted.include?(actual_str)
|
|
177
|
+
:correct
|
|
178
|
+
elsif partial_match?(predicted, actual_str)
|
|
179
|
+
:partial
|
|
180
|
+
else
|
|
181
|
+
:incorrect
|
|
182
|
+
end
|
|
183
|
+
rescue StandardError => e
|
|
184
|
+
handle_exception(e, level: :debug, operation: 'gaia.anticipation.grade_prediction',
|
|
185
|
+
prediction_id: prediction_id)
|
|
186
|
+
:incorrect
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def partial_match?(predicted, actual)
|
|
190
|
+
pred_words = predicted.split(/\s+/).reject(&:empty?)
|
|
191
|
+
actual_words = actual.split(/\s+/).reject(&:empty?)
|
|
192
|
+
return false if pred_words.empty? || actual_words.empty?
|
|
193
|
+
|
|
194
|
+
overlap = pred_words & actual_words
|
|
195
|
+
ratio = overlap.size.to_f / [pred_words.size, actual_words.size].min
|
|
196
|
+
ratio >= 0.5
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Returns true when the partner's affect baseline is below LOW_AFFECT_BASELINE.
|
|
200
|
+
def affect_baseline_low?(identity:)
|
|
201
|
+
return false unless defined?(Legion::Extensions::Agentic::Affect::Empathy::Helpers::ModelStore)
|
|
202
|
+
|
|
203
|
+
model_store = Legion::Extensions::Agentic::Affect::Empathy::Helpers::ModelStore.new
|
|
204
|
+
model = model_store.get(identity)
|
|
205
|
+
return false unless model.respond_to?(:emotional_state)
|
|
206
|
+
|
|
207
|
+
negative_states = %i[stressed anxious frustrated overwhelmed sad]
|
|
208
|
+
negative_states.include?(model.emotional_state)
|
|
209
|
+
rescue StandardError => e
|
|
210
|
+
handle_exception(e, level: :debug, operation: 'gaia.anticipation.affect_baseline_low', identity: identity)
|
|
211
|
+
false
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def extract_valence(anticipation)
|
|
215
|
+
ctx = anticipation[:emotional_context]
|
|
216
|
+
if ctx.is_a?(Hash)
|
|
217
|
+
ctx[:valence].to_f
|
|
218
|
+
elsif ctx.is_a?(Numeric)
|
|
219
|
+
ctx.to_f
|
|
220
|
+
else
|
|
221
|
+
0.0
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def high_entropy?(identity:)
|
|
226
|
+
return false unless defined?(Legion::Extensions::Agentic::Self::Helpers::IdentityFingerprint)
|
|
227
|
+
|
|
228
|
+
runner = Object.new
|
|
229
|
+
runner.extend(Legion::Extensions::Agentic::Self::Helpers::IdentityFingerprint)
|
|
230
|
+
entropy = runner.identity_entropy(identity: identity)
|
|
231
|
+
threshold = Legion::Gaia.settings&.dig(:identity, :high_entropy_threshold) || 0.7
|
|
232
|
+
entropy.to_f >= threshold
|
|
233
|
+
rescue StandardError
|
|
234
|
+
false
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Gaia
|
|
7
|
+
# H5 — fast per-response conflict detection (v1: correction veto only).
|
|
8
|
+
#
|
|
9
|
+
# Runs at the final-delivery hook (response_return). Deterministic pattern matching
|
|
10
|
+
# against high-strength correction traces and transform-tier behavioral synapses.
|
|
11
|
+
# No LLM calls. Must complete within the latency budget (default 50ms).
|
|
12
|
+
#
|
|
13
|
+
# The gut itself is a behavioral synapse (domain: 'gut') whose autonomy_mode gates
|
|
14
|
+
# what happens: observe = audit-only, filter = restraint directives, transform+ = veto.
|
|
15
|
+
# New partners always start in observe mode (emergent confidence 0.3 → observe tier).
|
|
16
|
+
module Gut
|
|
17
|
+
extend Legion::Logging::Helper
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
# Check for conflicts between draft response stats and known corrections/synapses.
|
|
22
|
+
#
|
|
23
|
+
# @param identity [String] partner identity
|
|
24
|
+
# @param draft_stats [Hash] stats about the draft response, e.g.
|
|
25
|
+
# { length_tokens:, format:, domain:, content_flags: [] }
|
|
26
|
+
# @return [Hash, nil]
|
|
27
|
+
# { conflict: true, confidence: Float, violated_trace_ids: [...], directive: Hash }
|
|
28
|
+
# { conflict: false }
|
|
29
|
+
# nil when gut check unavailable (no memory, no synapses, or latency exceeded)
|
|
30
|
+
def check(identity:, draft_stats:)
|
|
31
|
+
deadline = monotonic_now + (latency_budget_ms / 1000.0)
|
|
32
|
+
|
|
33
|
+
gut_synapse = Legion::Gaia::BehavioralSynapse.for(identity: identity.to_s, domain: 'gut')
|
|
34
|
+
gut_mode = gut_mode_for(gut_synapse)
|
|
35
|
+
|
|
36
|
+
return nil if gut_mode == :unavailable
|
|
37
|
+
|
|
38
|
+
violations = check_correction_traces(identity: identity.to_s, draft_stats: draft_stats,
|
|
39
|
+
deadline: deadline)
|
|
40
|
+
|
|
41
|
+
if violations.nil?
|
|
42
|
+
log.debug("[gut] latency budget exceeded identity=#{identity}")
|
|
43
|
+
return nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
synapse_violations = check_transform_synapses(identity: identity.to_s,
|
|
47
|
+
draft_stats: draft_stats,
|
|
48
|
+
deadline: deadline)
|
|
49
|
+
|
|
50
|
+
if synapse_violations.nil?
|
|
51
|
+
log.debug("[gut] latency budget exceeded (synapse phase) identity=#{identity}")
|
|
52
|
+
return nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
all_violations = violations + synapse_violations
|
|
56
|
+
return { conflict: false } if all_violations.empty?
|
|
57
|
+
|
|
58
|
+
violated_trace_ids = all_violations.flat_map { |v| v[:trace_ids] }.uniq
|
|
59
|
+
confidence = all_violations.map { |v| v[:strength] }.max.to_f
|
|
60
|
+
directive = all_violations.first[:directive] || {}
|
|
61
|
+
|
|
62
|
+
log.info("[gut] conflict detected identity=#{identity} " \
|
|
63
|
+
"violations=#{all_violations.size} confidence=#{confidence.round(3)} " \
|
|
64
|
+
"mode=#{gut_mode}")
|
|
65
|
+
|
|
66
|
+
{ conflict: true, confidence: confidence, violated_trace_ids: violated_trace_ids, directive: directive }
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
handle_exception(e, level: :warn, operation: 'gaia.gut.check', identity: identity)
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def latency_budget_ms
|
|
73
|
+
settings = Legion::Gaia.settings
|
|
74
|
+
settings&.dig(:gut, :latency_budget_ms) || 50.0
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def monotonic_now
|
|
78
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def deadline_exceeded?(deadline)
|
|
82
|
+
monotonic_now >= deadline
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns the autonomy mode for the gut synapse, or :unavailable if gut should not run.
|
|
86
|
+
# New partners get :observe (observe = audit only, no veto action).
|
|
87
|
+
def gut_mode_for(gut_synapse)
|
|
88
|
+
return :observe if gut_synapse.nil?
|
|
89
|
+
|
|
90
|
+
Legion::Gaia::BehavioralSynapse::Math.autonomy_mode(gut_synapse[:confidence].to_f)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Compare draft_stats against high-strength correction traces for the partner.
|
|
94
|
+
# Returns array of violation hashes, or nil if deadline exceeded.
|
|
95
|
+
def check_correction_traces(identity:, draft_stats:, deadline:)
|
|
96
|
+
return [] unless defined?(Legion::Extensions::Agentic::Memory::Trace::Runners::Traces)
|
|
97
|
+
|
|
98
|
+
return nil if deadline_exceeded?(deadline)
|
|
99
|
+
|
|
100
|
+
runner = Object.new
|
|
101
|
+
runner.extend(Legion::Extensions::Agentic::Memory::Trace::Runners::Traces)
|
|
102
|
+
|
|
103
|
+
result = runner.retrieve_by_domain(
|
|
104
|
+
domain_tag: "partner:#{identity}",
|
|
105
|
+
min_strength: correction_min_strength,
|
|
106
|
+
limit: correction_trace_limit
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return nil if deadline_exceeded?(deadline)
|
|
110
|
+
|
|
111
|
+
correction_traces = Array(result[:traces]).select { |t| correction_trace?(t) }
|
|
112
|
+
violations = []
|
|
113
|
+
|
|
114
|
+
correction_traces.each do |trace|
|
|
115
|
+
return nil if deadline_exceeded?(deadline)
|
|
116
|
+
|
|
117
|
+
next unless trace_conflicts_with_draft?(trace, draft_stats)
|
|
118
|
+
|
|
119
|
+
violations << {
|
|
120
|
+
trace_ids: [trace[:trace_id].to_s],
|
|
121
|
+
strength: trace[:strength].to_f,
|
|
122
|
+
directive: directive_from_trace(trace)
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
violations
|
|
127
|
+
rescue StandardError => e
|
|
128
|
+
handle_exception(e, level: :debug, operation: 'gaia.gut.check_correction_traces', identity: identity)
|
|
129
|
+
[]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Check transform-tier behavioral synapses for conflicts with draft stats.
|
|
133
|
+
# Returns array of violation hashes, or nil if deadline exceeded.
|
|
134
|
+
def check_transform_synapses(identity:, draft_stats:, deadline:)
|
|
135
|
+
return nil if deadline_exceeded?(deadline)
|
|
136
|
+
|
|
137
|
+
synapses = Legion::Gaia::BehavioralSynapse.all_for(identity: identity)
|
|
138
|
+
transform_synapses = synapses.select do |s|
|
|
139
|
+
mode = Legion::Gaia::BehavioralSynapse::Math.autonomy_mode(s[:confidence].to_f)
|
|
140
|
+
%i[transform autonomous].include?(mode)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
violations = []
|
|
144
|
+
|
|
145
|
+
transform_synapses.each do |synapse|
|
|
146
|
+
return nil if deadline_exceeded?(deadline)
|
|
147
|
+
|
|
148
|
+
next unless synapse_conflicts_with_draft?(synapse, draft_stats)
|
|
149
|
+
|
|
150
|
+
violations << {
|
|
151
|
+
trace_ids: Array(synapse[:evidence_trace_ids]),
|
|
152
|
+
strength: synapse[:confidence].to_f,
|
|
153
|
+
directive: synapse[:directive].is_a?(Hash) ? synapse[:directive] : {}
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
violations
|
|
158
|
+
rescue StandardError => e
|
|
159
|
+
handle_exception(e, level: :debug, operation: 'gaia.gut.check_transform_synapses', identity: identity)
|
|
160
|
+
[]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def correction_trace?(trace)
|
|
164
|
+
return false unless trace.is_a?(Hash)
|
|
165
|
+
|
|
166
|
+
trace[:trace_type].to_s == 'correction' ||
|
|
167
|
+
Array(trace[:domain_tags]).any? { |tag| tag.to_s == 'correction' }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Checks if a correction trace conflicts with the draft stats.
|
|
171
|
+
# Conflict = the draft exhibits a pattern that the correction explicitly negated.
|
|
172
|
+
def trace_conflicts_with_draft?(trace, draft_stats)
|
|
173
|
+
payload = trace[:content_payload]
|
|
174
|
+
return false unless payload.is_a?(Hash)
|
|
175
|
+
|
|
176
|
+
stats = draft_stats.is_a?(Hash) ? draft_stats : {}
|
|
177
|
+
trace_verbosity_conflict?(payload, stats) ||
|
|
178
|
+
trace_format_conflict?(payload, stats) ||
|
|
179
|
+
trace_content_flag_conflict?(payload, stats) ||
|
|
180
|
+
trace_domain_conflict?(payload, stats)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def trace_verbosity_conflict?(payload, stats)
|
|
184
|
+
type = payload[:correction_type].to_s
|
|
185
|
+
return false unless type.include?('verbosity') || type.include?('length')
|
|
186
|
+
|
|
187
|
+
budget = payload[:budget_tokens] || payload[:max_tokens]
|
|
188
|
+
budget && stats[:length_tokens].to_i > budget.to_i
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def trace_format_conflict?(payload, stats)
|
|
192
|
+
return false unless payload[:rejected_format]
|
|
193
|
+
|
|
194
|
+
stats[:format].to_s == payload[:rejected_format].to_s
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def trace_content_flag_conflict?(payload, stats)
|
|
198
|
+
return false unless payload[:rejected_content_flags].is_a?(Array)
|
|
199
|
+
|
|
200
|
+
draft_flags = Array(stats[:content_flags]).map(&:to_s)
|
|
201
|
+
rejected = payload[:rejected_content_flags].map(&:to_s)
|
|
202
|
+
draft_flags.intersect?(rejected)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def trace_domain_conflict?(payload, stats)
|
|
206
|
+
return false unless payload[:correction_type].to_s == 'explicit_negative_feedback'
|
|
207
|
+
|
|
208
|
+
applied_domains = Array(stats[:applied_domains]).map(&:to_s)
|
|
209
|
+
blocked_domains = Array(payload[:blocked_domains]).map(&:to_s)
|
|
210
|
+
blocked_domains.any? && applied_domains.intersect?(blocked_domains)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Checks if a transform-tier synapse's directive conflicts with draft stats.
|
|
214
|
+
def synapse_conflicts_with_draft?(synapse, draft_stats)
|
|
215
|
+
directive = synapse[:directive]
|
|
216
|
+
return false unless directive.is_a?(Hash)
|
|
217
|
+
|
|
218
|
+
stats = draft_stats.is_a?(Hash) ? draft_stats : {}
|
|
219
|
+
synapse_budget_conflict?(directive, stats) || synapse_format_conflict?(directive, stats)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def synapse_budget_conflict?(directive, stats)
|
|
223
|
+
return false unless directive[:budget_ratio]
|
|
224
|
+
|
|
225
|
+
baseline = stats[:baseline_tokens].to_i
|
|
226
|
+
draft_len = stats[:length_tokens].to_i
|
|
227
|
+
allowed = (baseline * directive[:budget_ratio].to_f).to_i
|
|
228
|
+
allowed.positive? && draft_len > allowed
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def synapse_format_conflict?(directive, stats)
|
|
232
|
+
return false unless directive[:format] && stats[:format]
|
|
233
|
+
|
|
234
|
+
stats[:format].to_s != directive[:format].to_s
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def directive_from_trace(trace)
|
|
238
|
+
payload = trace[:content_payload]
|
|
239
|
+
return {} unless payload.is_a?(Hash)
|
|
240
|
+
|
|
241
|
+
dir = {}
|
|
242
|
+
dir[:budget_tokens] = payload[:budget_tokens] if payload[:budget_tokens]
|
|
243
|
+
dir[:rejected_format] = payload[:rejected_format] if payload[:rejected_format]
|
|
244
|
+
dir[:correction_type] = payload[:correction_type] if payload[:correction_type]
|
|
245
|
+
dir
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def correction_min_strength
|
|
249
|
+
settings = Legion::Gaia.settings
|
|
250
|
+
settings&.dig(:gut, :correction_min_strength) || 0.6
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def correction_trace_limit
|
|
254
|
+
settings = Legion::Gaia.settings
|
|
255
|
+
settings&.dig(:gut, :correction_trace_limit) || 20
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
data/lib/legion/gaia/settings.rb
CHANGED
|
@@ -16,7 +16,9 @@ module Legion
|
|
|
16
16
|
session: { persistence: 'auto', ttl: 86_400 },
|
|
17
17
|
output: { mobile_max_length: 500, suggest_channel_switch: true },
|
|
18
18
|
notifications: default_notifications,
|
|
19
|
-
partner: default_partner
|
|
19
|
+
partner: default_partner,
|
|
20
|
+
gut: default_gut,
|
|
21
|
+
anticipation: default_anticipation
|
|
20
22
|
}
|
|
21
23
|
end
|
|
22
24
|
|
|
@@ -39,6 +41,20 @@ module Legion
|
|
|
39
41
|
}
|
|
40
42
|
end
|
|
41
43
|
|
|
44
|
+
def default_gut
|
|
45
|
+
{
|
|
46
|
+
latency_budget_ms: 50.0,
|
|
47
|
+
correction_min_strength: 0.6,
|
|
48
|
+
correction_trace_limit: 20
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def default_anticipation
|
|
53
|
+
{
|
|
54
|
+
actionable_threshold: 0.65
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
42
58
|
def default_notifications
|
|
43
59
|
{
|
|
44
60
|
enabled: true,
|
data/lib/legion/gaia/version.rb
CHANGED
data/lib/legion/gaia.rb
CHANGED
|
@@ -34,6 +34,8 @@ require 'legion/gaia/proactive_dispatcher'
|
|
|
34
34
|
require 'legion/gaia/bond_registry'
|
|
35
35
|
require 'legion/gaia/death_protocol'
|
|
36
36
|
require 'legion/gaia/behavioral_synapse'
|
|
37
|
+
require 'legion/gaia/gut'
|
|
38
|
+
require 'legion/gaia/anticipation'
|
|
37
39
|
require 'legion/gaia/partner_model'
|
|
38
40
|
require 'legion/gaia/disclosure'
|
|
39
41
|
require 'legion/gaia/visible_growth'
|
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.
|
|
4
|
+
version: 0.9.67
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -416,6 +416,7 @@ files:
|
|
|
416
416
|
- lib/legion/gaia.rb
|
|
417
417
|
- lib/legion/gaia/actors/heartbeat.rb
|
|
418
418
|
- lib/legion/gaia/advisory.rb
|
|
419
|
+
- lib/legion/gaia/anticipation.rb
|
|
419
420
|
- lib/legion/gaia/audit_observer.rb
|
|
420
421
|
- lib/legion/gaia/behavioral_synapse.rb
|
|
421
422
|
- lib/legion/gaia/bond_registry.rb
|
|
@@ -434,6 +435,7 @@ files:
|
|
|
434
435
|
- lib/legion/gaia/cognitive_bus.rb
|
|
435
436
|
- lib/legion/gaia/death_protocol.rb
|
|
436
437
|
- lib/legion/gaia/disclosure.rb
|
|
438
|
+
- lib/legion/gaia/gut.rb
|
|
437
439
|
- lib/legion/gaia/input_frame.rb
|
|
438
440
|
- lib/legion/gaia/intent_classifier.rb
|
|
439
441
|
- lib/legion/gaia/logging.rb
|