lex-synapse 0.3.2 → 0.4.0
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 +14 -0
- data/CLAUDE.md +1 -1
- data/lib/legion/extensions/synapse/actors/challenge.rb +31 -0
- data/lib/legion/extensions/synapse/client.rb +21 -0
- data/lib/legion/extensions/synapse/data/migrations/005_add_synapse_challenges.rb +33 -0
- data/lib/legion/extensions/synapse/data/models/synapse_challenge.rb +26 -0
- data/lib/legion/extensions/synapse/helpers/challenge.rb +55 -0
- data/lib/legion/extensions/synapse/helpers/proposals.rb +1 -1
- data/lib/legion/extensions/synapse/runners/challenge.rb +249 -0
- data/lib/legion/extensions/synapse/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c62483ced1238be0da450c077108fa58541aa5964b27f3c6977ad11203202115
|
|
4
|
+
data.tar.gz: c8c8a52ff8eb60ebd971a4eeae4ae0a53709629548a58c4409bbdd7c5d3cfebe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50c4b74beca7a56c91953d34dd8179cd7f1c42af111ef05be4c9ec62f154135e93fac708c0270ed9c47ee73be987dd8386c5c47b638988982ff454980a4f2e0e
|
|
7
|
+
data.tar.gz: 3b6683e9c804a465f47d5fde8471768896ea7c4554e597a8a26c03313fb32e3d30c97cfa9e071726997455a3a5d4ea4392e1d9d94a20ce9ccc03c74de6318a2a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2026-03-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Adversarial challenge phase for proposals: conflict detection, LLM challenge, weighted aggregation
|
|
7
|
+
- `synapse_challenges` table (migration 005) for per-challenge verdict tracking
|
|
8
|
+
- `Runners::Challenge` with challenge_proposal, resolve_challenge_outcomes, run_challenge_cycle
|
|
9
|
+
- `Helpers::Challenge` with settings, constants, impact threshold helpers
|
|
10
|
+
- `Actors::Challenge` polling every 60s for pending proposals
|
|
11
|
+
- Challenger confidence tracking with outcome-based learning loop
|
|
12
|
+
- Auto-accept/auto-reject thresholds for unanimous verdicts
|
|
13
|
+
- Client methods: challenge_proposal, challenges, challenger_stats
|
|
14
|
+
- New proposal statuses: auto_accepted, auto_rejected
|
|
15
|
+
- Impact scoring gates LLM challenge (expensive calls only for high-impact proposals)
|
|
16
|
+
|
|
3
17
|
## [0.3.2] - 2026-03-21
|
|
4
18
|
|
|
5
19
|
### Fixed
|
data/CLAUDE.md
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Synapse
|
|
6
|
+
module Actor
|
|
7
|
+
class Challenge < Legion::Extensions::Actors::Every
|
|
8
|
+
def runner_function
|
|
9
|
+
'run_challenge_cycle'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def time
|
|
13
|
+
60
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def use_runner?
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def check_subtask?
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def generate_task?
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -11,6 +11,7 @@ require_relative 'runners/dream'
|
|
|
11
11
|
require_relative 'runners/promote'
|
|
12
12
|
require_relative 'runners/retrieve'
|
|
13
13
|
require_relative 'runners/propose'
|
|
14
|
+
require_relative 'runners/challenge'
|
|
14
15
|
require_relative 'data/models/synapse_proposal'
|
|
15
16
|
require_relative 'helpers/proposals'
|
|
16
17
|
|
|
@@ -29,6 +30,7 @@ module Legion
|
|
|
29
30
|
include Runners::Promote
|
|
30
31
|
include Runners::Retrieve
|
|
31
32
|
include Runners::Propose
|
|
33
|
+
include Runners::Challenge
|
|
32
34
|
|
|
33
35
|
attr_reader :conditioner_client, :transformer_client
|
|
34
36
|
|
|
@@ -79,6 +81,25 @@ module Legion
|
|
|
79
81
|
proposal.update(status: status, reviewed_at: Time.now)
|
|
80
82
|
{ success: true, proposal_id: proposal_id, status: status }
|
|
81
83
|
end
|
|
84
|
+
|
|
85
|
+
def challenge_proposal(proposal_id:)
|
|
86
|
+
super(proposal_id: proposal_id, transformer_client: @transformer_client)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def challenges(proposal_id:)
|
|
90
|
+
Data::Model.define_synapse_challenge_model
|
|
91
|
+
Data::Model::SynapseChallenge.where(proposal_id: proposal_id).order(Sequel.desc(:id)).all
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def challenger_stats
|
|
95
|
+
Data::Model.define_synapse_challenge_model
|
|
96
|
+
resolved = Data::Model::SynapseChallenge.exclude(outcome: nil)
|
|
97
|
+
{
|
|
98
|
+
total: resolved.count,
|
|
99
|
+
correct: resolved.where(outcome: 'correct').count,
|
|
100
|
+
by_type: resolved.group_and_count(:challenger_type).to_h { |r| [r[:challenger_type], r[:count]] }
|
|
101
|
+
}
|
|
102
|
+
end
|
|
82
103
|
end
|
|
83
104
|
end
|
|
84
105
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
create_table(:synapse_challenges) do
|
|
6
|
+
primary_key :id
|
|
7
|
+
foreign_key :proposal_id, :synapse_proposals, null: false, index: true
|
|
8
|
+
String :challenger_type, null: false, size: 50
|
|
9
|
+
String :verdict, null: false, size: 50
|
|
10
|
+
String :reasoning, text: true
|
|
11
|
+
Float :challenger_confidence, default: 0.5
|
|
12
|
+
DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
13
|
+
DateTime :resolved_at
|
|
14
|
+
String :outcome, size: 50
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
alter_table(:synapse_proposals) do
|
|
18
|
+
add_column :challenge_state, String, size: 50
|
|
19
|
+
add_column :challenge_score, Float
|
|
20
|
+
add_column :impact_score, Float
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
down do
|
|
25
|
+
drop_table :synapse_challenges
|
|
26
|
+
|
|
27
|
+
alter_table(:synapse_proposals) do
|
|
28
|
+
drop_column :challenge_state
|
|
29
|
+
drop_column :challenge_score
|
|
30
|
+
drop_column :impact_score
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Synapse
|
|
6
|
+
module Data
|
|
7
|
+
module Model
|
|
8
|
+
def self.define_synapse_challenge_model
|
|
9
|
+
return if const_defined?(:SynapseChallenge, false)
|
|
10
|
+
return unless defined?(Legion::Data) && Legion::Settings.dig(:data, :connected)
|
|
11
|
+
|
|
12
|
+
db = Sequel::Model.db
|
|
13
|
+
return unless db&.table_exists?(:synapse_challenges)
|
|
14
|
+
|
|
15
|
+
klass = Class.new(Sequel::Model(:synapse_challenges)) do
|
|
16
|
+
many_to_one :proposal, class: 'Legion::Extensions::Synapse::Data::Model::SynapseProposal',
|
|
17
|
+
key: :proposal_id
|
|
18
|
+
end
|
|
19
|
+
klass.set_primary_key :id
|
|
20
|
+
const_set(:SynapseChallenge, klass)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Synapse
|
|
6
|
+
module Helpers
|
|
7
|
+
module Challenge
|
|
8
|
+
VALID_VERDICTS = %w[support challenge abstain].freeze
|
|
9
|
+
VALID_CHALLENGER_TYPES = %w[conflict llm].freeze
|
|
10
|
+
VALID_OUTCOMES = %w[correct incorrect].freeze
|
|
11
|
+
VALID_CHALLENGE_STATES = %w[challenging challenged].freeze
|
|
12
|
+
|
|
13
|
+
IMPACT_WEIGHTS = {
|
|
14
|
+
'llm_transform' => 0.7,
|
|
15
|
+
'transform_mutation' => 0.5,
|
|
16
|
+
'attention_mutation' => 0.6,
|
|
17
|
+
'route_change' => 0.8
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
DEFAULT_SETTINGS = {
|
|
21
|
+
enabled: true,
|
|
22
|
+
impact_threshold: 0.3,
|
|
23
|
+
auto_accept_threshold: 0.85,
|
|
24
|
+
auto_reject_threshold: 0.15,
|
|
25
|
+
llm_engine_options: { temperature: 0.2, max_tokens: 512 },
|
|
26
|
+
outcome_observation_window: 50,
|
|
27
|
+
max_per_cycle: 5,
|
|
28
|
+
challenger_starting_confidence: 0.5,
|
|
29
|
+
challenger_correct_adjustment: 0.05,
|
|
30
|
+
challenger_incorrect_adjustment: -0.08
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
def settings
|
|
35
|
+
raw = Legion::Settings.dig('lex-synapse', 'challenge')
|
|
36
|
+
return DEFAULT_SETTINGS.dup unless raw.is_a?(Hash)
|
|
37
|
+
|
|
38
|
+
merged = DEFAULT_SETTINGS.dup
|
|
39
|
+
raw.each { |k, v| merged[k.to_sym] = v unless v.nil? }
|
|
40
|
+
merged
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def enabled?
|
|
44
|
+
settings[:enabled] == true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def above_impact_threshold?(impact_score)
|
|
48
|
+
impact_score >= settings[:impact_threshold]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -7,7 +7,7 @@ module Legion
|
|
|
7
7
|
module Proposals
|
|
8
8
|
VALID_PROPOSAL_TYPES = %w[llm_transform attention_mutation transform_mutation route_change].freeze
|
|
9
9
|
VALID_TRIGGERS = %w[reactive proactive].freeze
|
|
10
|
-
VALID_STATUSES = %w[pending approved rejected applied expired].freeze
|
|
10
|
+
VALID_STATUSES = %w[pending challenging challenged approved rejected applied expired auto_accepted auto_rejected].freeze
|
|
11
11
|
|
|
12
12
|
DEFAULT_SETTINGS = {
|
|
13
13
|
enabled: true,
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/challenge'
|
|
4
|
+
require_relative '../helpers/confidence'
|
|
5
|
+
require_relative '../data/models/synapse'
|
|
6
|
+
require_relative '../data/models/synapse_proposal'
|
|
7
|
+
require_relative '../data/models/synapse_challenge'
|
|
8
|
+
require_relative '../data/models/synapse_signal'
|
|
9
|
+
|
|
10
|
+
module Legion
|
|
11
|
+
module Extensions
|
|
12
|
+
module Synapse
|
|
13
|
+
module Runners
|
|
14
|
+
module Challenge
|
|
15
|
+
def pending_challenges
|
|
16
|
+
Data::Model.define_synapse_proposal_model
|
|
17
|
+
Data::Model::SynapseProposal.where(status: 'pending', challenge_state: nil)
|
|
18
|
+
.order(Sequel.asc(:id)).all
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def challenge_proposal(proposal_id:, transformer_client: nil)
|
|
22
|
+
Data::Model.define_synapse_proposal_model
|
|
23
|
+
Data::Model.define_synapse_challenge_model
|
|
24
|
+
Data::Model.define_synapse_model
|
|
25
|
+
Data::Model.define_synapse_signal_model
|
|
26
|
+
|
|
27
|
+
return { success: true, skipped: true } unless Helpers::Challenge.enabled?
|
|
28
|
+
|
|
29
|
+
proposal = Data::Model::SynapseProposal[proposal_id]
|
|
30
|
+
return { success: false, error: 'proposal not found' } unless proposal
|
|
31
|
+
return { success: false, error: 'proposal not challengeable' } unless proposal.status == 'pending' && proposal.challenge_state.nil?
|
|
32
|
+
|
|
33
|
+
synapse = Data::Model::Synapse[proposal.synapse_id]
|
|
34
|
+
return { success: false, error: 'synapse not found' } unless synapse
|
|
35
|
+
|
|
36
|
+
proposal.update(challenge_state: 'challenging')
|
|
37
|
+
|
|
38
|
+
impact = calculate_impact_score(proposal, synapse)
|
|
39
|
+
proposal.update(impact_score: impact)
|
|
40
|
+
|
|
41
|
+
conflict_check(proposal)
|
|
42
|
+
|
|
43
|
+
llm_challenge(proposal, synapse, transformer_client) if Helpers::Challenge.above_impact_threshold?(impact) && transformer_client
|
|
44
|
+
|
|
45
|
+
aggregate_challenges(proposal)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def resolve_challenge_outcomes(proposal_id:)
|
|
49
|
+
Data::Model.define_synapse_proposal_model
|
|
50
|
+
Data::Model.define_synapse_challenge_model
|
|
51
|
+
Data::Model.define_synapse_signal_model
|
|
52
|
+
|
|
53
|
+
proposal = Data::Model::SynapseProposal[proposal_id]
|
|
54
|
+
return { success: false, error: 'proposal not found' } unless proposal
|
|
55
|
+
return { success: false, error: 'proposal not applied' } unless proposal.status == 'applied'
|
|
56
|
+
|
|
57
|
+
settings = Helpers::Challenge.settings
|
|
58
|
+
window = settings[:outcome_observation_window] || 50
|
|
59
|
+
|
|
60
|
+
signals = Data::Model::SynapseSignal.where(synapse_id: proposal.synapse_id)
|
|
61
|
+
.order(Sequel.desc(:id)).limit(window).all
|
|
62
|
+
return { success: false, error: 'insufficient signals' } if signals.size < window
|
|
63
|
+
|
|
64
|
+
success_rate = signals.count(&:transform_success).to_f / signals.size
|
|
65
|
+
proposal_succeeded = success_rate >= 0.7
|
|
66
|
+
|
|
67
|
+
challenges = Data::Model::SynapseChallenge.where(proposal_id: proposal_id)
|
|
68
|
+
.exclude(verdict: 'abstain').all
|
|
69
|
+
|
|
70
|
+
challenges.each do |challenge|
|
|
71
|
+
supported = challenge.verdict == 'support'
|
|
72
|
+
correct = (supported && proposal_succeeded) || (!supported && !proposal_succeeded)
|
|
73
|
+
outcome = correct ? 'correct' : 'incorrect'
|
|
74
|
+
|
|
75
|
+
adj = correct ? settings[:challenger_correct_adjustment] : settings[:challenger_incorrect_adjustment]
|
|
76
|
+
new_conf = (challenge.challenger_confidence + adj).clamp(0.0, 1.0)
|
|
77
|
+
|
|
78
|
+
challenge.update(outcome: outcome, challenger_confidence: new_conf, resolved_at: Time.now)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
{ success: true, proposal_id: proposal_id, success_rate: success_rate, resolved: challenges.size }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def run_challenge_cycle(transformer_client: nil)
|
|
85
|
+
Data::Model.define_synapse_proposal_model
|
|
86
|
+
Data::Model.define_synapse_challenge_model
|
|
87
|
+
return { challenged: 0, resolved: 0 } unless Helpers::Challenge.enabled?
|
|
88
|
+
|
|
89
|
+
settings = Helpers::Challenge.settings
|
|
90
|
+
max = settings[:max_per_cycle] || 5
|
|
91
|
+
|
|
92
|
+
challenged = 0
|
|
93
|
+
pending_challenges.first(max).each do |proposal|
|
|
94
|
+
challenge_proposal(proposal_id: proposal.id, transformer_client: transformer_client)
|
|
95
|
+
challenged += 1
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
resolved = 0
|
|
99
|
+
window = settings[:outcome_observation_window] || 50
|
|
100
|
+
Data::Model.define_synapse_signal_model
|
|
101
|
+
Data::Model::SynapseProposal.where(status: 'applied').each do |proposal|
|
|
102
|
+
cutoff = proposal.respond_to?(:reviewed_at) && proposal.reviewed_at ? proposal.reviewed_at : proposal.created_at
|
|
103
|
+
post_signals = Data::Model::SynapseSignal.where(synapse_id: proposal.synapse_id)
|
|
104
|
+
.where { created_at >= cutoff }.count
|
|
105
|
+
next unless post_signals >= window
|
|
106
|
+
|
|
107
|
+
unresolved = Data::Model::SynapseChallenge.where(proposal_id: proposal.id, outcome: nil)
|
|
108
|
+
.exclude(verdict: 'abstain')
|
|
109
|
+
next unless unresolved.any?
|
|
110
|
+
|
|
111
|
+
resolve_challenge_outcomes(proposal_id: proposal.id)
|
|
112
|
+
resolved += 1
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
{ challenged: challenged, resolved: resolved }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def conflict_check(proposal)
|
|
121
|
+
conflicts = Data::Model::SynapseProposal.where(
|
|
122
|
+
synapse_id: proposal.synapse_id,
|
|
123
|
+
proposal_type: proposal.proposal_type,
|
|
124
|
+
status: 'pending'
|
|
125
|
+
).exclude(id: proposal.id)
|
|
126
|
+
|
|
127
|
+
if conflicts.any?
|
|
128
|
+
Data::Model::SynapseChallenge.create(
|
|
129
|
+
proposal_id: proposal.id, challenger_type: 'conflict',
|
|
130
|
+
verdict: 'challenge',
|
|
131
|
+
reasoning: "#{conflicts.count} conflicting #{proposal.proposal_type} proposal(s) pending on same synapse",
|
|
132
|
+
challenger_confidence: Helpers::Challenge.settings[:challenger_starting_confidence]
|
|
133
|
+
)
|
|
134
|
+
else
|
|
135
|
+
Data::Model::SynapseChallenge.create(
|
|
136
|
+
proposal_id: proposal.id, challenger_type: 'conflict',
|
|
137
|
+
verdict: 'support', reasoning: 'no conflicting proposals',
|
|
138
|
+
challenger_confidence: Helpers::Challenge.settings[:challenger_starting_confidence]
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def llm_challenge(proposal, synapse, transformer_client)
|
|
144
|
+
prompt = build_challenge_prompt(proposal, synapse)
|
|
145
|
+
engine_options = Helpers::Challenge.settings[:llm_engine_options]
|
|
146
|
+
|
|
147
|
+
result = transformer_client.transform(
|
|
148
|
+
transformation: prompt, payload: {}, engine: :llm, engine_options: engine_options
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
verdict, reasoning = parse_llm_verdict(result[:success] ? result[:result] : nil)
|
|
152
|
+
|
|
153
|
+
llm_confidence = rolling_llm_confidence
|
|
154
|
+
Data::Model::SynapseChallenge.create(
|
|
155
|
+
proposal_id: proposal.id, challenger_type: 'llm',
|
|
156
|
+
verdict: verdict, reasoning: reasoning,
|
|
157
|
+
challenger_confidence: llm_confidence
|
|
158
|
+
)
|
|
159
|
+
rescue StandardError => e
|
|
160
|
+
Legion::Logging.warn("Challenge LLM call failed: #{e.message}") if defined?(Legion::Logging)
|
|
161
|
+
Data::Model::SynapseChallenge.create(
|
|
162
|
+
proposal_id: proposal.id, challenger_type: 'llm',
|
|
163
|
+
verdict: 'abstain', reasoning: "LLM error: #{e.message}",
|
|
164
|
+
challenger_confidence: Helpers::Challenge.settings[:challenger_starting_confidence]
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def aggregate_challenges(proposal)
|
|
169
|
+
challenges = Data::Model::SynapseChallenge.where(proposal_id: proposal.id)
|
|
170
|
+
.exclude(verdict: 'abstain').all
|
|
171
|
+
|
|
172
|
+
if challenges.empty?
|
|
173
|
+
proposal.update(challenge_state: 'challenged', challenge_score: 0.5)
|
|
174
|
+
return { success: true, challenge_score: 0.5, decision: 'challenged' }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
support_weight = challenges.select { |c| c.verdict == 'support' }.sum(&:challenger_confidence)
|
|
178
|
+
challenge_weight = challenges.select { |c| c.verdict == 'challenge' }.sum(&:challenger_confidence)
|
|
179
|
+
total = support_weight + challenge_weight
|
|
180
|
+
|
|
181
|
+
score = total.zero? ? 0.5 : support_weight / total
|
|
182
|
+
|
|
183
|
+
settings = Helpers::Challenge.settings
|
|
184
|
+
decision = if score >= settings[:auto_accept_threshold]
|
|
185
|
+
proposal.update(status: 'auto_accepted', challenge_state: 'challenged', challenge_score: score)
|
|
186
|
+
'auto_accepted'
|
|
187
|
+
elsif score <= settings[:auto_reject_threshold]
|
|
188
|
+
proposal.update(status: 'auto_rejected', challenge_state: 'challenged', challenge_score: score)
|
|
189
|
+
'auto_rejected'
|
|
190
|
+
else
|
|
191
|
+
proposal.update(challenge_state: 'challenged', challenge_score: score)
|
|
192
|
+
'challenged'
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
{ success: true, challenge_score: score, decision: decision }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def calculate_impact_score(proposal, synapse)
|
|
199
|
+
base = Helpers::Challenge::IMPACT_WEIGHTS.fetch(proposal.proposal_type, 0.5)
|
|
200
|
+
recent_signals = Data::Model::SynapseSignal.where(synapse_id: synapse.id).count
|
|
201
|
+
baseline = [synapse.respond_to?(:baseline_throughput) && synapse.baseline_throughput ? synapse.baseline_throughput : 1.0, 1.0].max
|
|
202
|
+
throughput_factor = [recent_signals.to_f / baseline, 2.0].min
|
|
203
|
+
|
|
204
|
+
(base * synapse.confidence * throughput_factor).clamp(0.0, 1.0)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def build_challenge_prompt(proposal, synapse)
|
|
208
|
+
"Evaluate this proposed change to a cognitive routing synapse.\n\n" \
|
|
209
|
+
"Synapse confidence: #{synapse.confidence}\n" \
|
|
210
|
+
"Proposal type: #{proposal.proposal_type}\n" \
|
|
211
|
+
"Rationale: #{proposal.rationale}\n" \
|
|
212
|
+
"Proposed inputs: #{proposal.inputs}\n" \
|
|
213
|
+
"Proposed output: #{proposal.output}\n\n" \
|
|
214
|
+
"Is this change sound? Respond in exactly this format:\n" \
|
|
215
|
+
"VERDICT: SUPPORT or CHALLENGE or ABSTAIN\n" \
|
|
216
|
+
'REASONING: one sentence explanation'
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def parse_llm_verdict(response)
|
|
220
|
+
return ['abstain', 'no LLM response'] unless response.is_a?(String)
|
|
221
|
+
|
|
222
|
+
text = response.to_s.strip
|
|
223
|
+
verdict = if text.match?(/VERDICT:\s*SUPPORT/i)
|
|
224
|
+
'support'
|
|
225
|
+
elsif text.match?(/VERDICT:\s*CHALLENGE/i)
|
|
226
|
+
'challenge'
|
|
227
|
+
else
|
|
228
|
+
'abstain'
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
reasoning_match = text.match(/REASONING:\s*(.+)/i)
|
|
232
|
+
reasoning = reasoning_match ? reasoning_match[1].strip : text.slice(0, 200)
|
|
233
|
+
|
|
234
|
+
[verdict, reasoning]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def rolling_llm_confidence
|
|
238
|
+
recent = Data::Model::SynapseChallenge.where(challenger_type: 'llm')
|
|
239
|
+
.exclude(outcome: nil)
|
|
240
|
+
.order(Sequel.desc(:id)).limit(20).all
|
|
241
|
+
return Helpers::Challenge.settings[:challenger_starting_confidence] if recent.empty?
|
|
242
|
+
|
|
243
|
+
recent.first.challenger_confidence
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-synapse
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -56,6 +56,7 @@ files:
|
|
|
56
56
|
- Rakefile
|
|
57
57
|
- lex-synapse.gemspec
|
|
58
58
|
- lib/legion/extensions/synapse.rb
|
|
59
|
+
- lib/legion/extensions/synapse/actors/challenge.rb
|
|
59
60
|
- lib/legion/extensions/synapse/actors/crystallize.rb
|
|
60
61
|
- lib/legion/extensions/synapse/actors/decay.rb
|
|
61
62
|
- lib/legion/extensions/synapse/actors/evaluate.rb
|
|
@@ -67,14 +68,18 @@ files:
|
|
|
67
68
|
- lib/legion/extensions/synapse/data/migrations/002_create_synapse_mutations.rb
|
|
68
69
|
- lib/legion/extensions/synapse/data/migrations/003_create_synapse_signals.rb
|
|
69
70
|
- lib/legion/extensions/synapse/data/migrations/004_create_synapse_proposals.rb
|
|
71
|
+
- lib/legion/extensions/synapse/data/migrations/005_add_synapse_challenges.rb
|
|
70
72
|
- lib/legion/extensions/synapse/data/models/synapse.rb
|
|
73
|
+
- lib/legion/extensions/synapse/data/models/synapse_challenge.rb
|
|
71
74
|
- lib/legion/extensions/synapse/data/models/synapse_mutation.rb
|
|
72
75
|
- lib/legion/extensions/synapse/data/models/synapse_proposal.rb
|
|
73
76
|
- lib/legion/extensions/synapse/data/models/synapse_signal.rb
|
|
77
|
+
- lib/legion/extensions/synapse/helpers/challenge.rb
|
|
74
78
|
- lib/legion/extensions/synapse/helpers/confidence.rb
|
|
75
79
|
- lib/legion/extensions/synapse/helpers/homeostasis.rb
|
|
76
80
|
- lib/legion/extensions/synapse/helpers/proposals.rb
|
|
77
81
|
- lib/legion/extensions/synapse/helpers/relationship_wrapper.rb
|
|
82
|
+
- lib/legion/extensions/synapse/runners/challenge.rb
|
|
78
83
|
- lib/legion/extensions/synapse/runners/crystallize.rb
|
|
79
84
|
- lib/legion/extensions/synapse/runners/dream.rb
|
|
80
85
|
- lib/legion/extensions/synapse/runners/evaluate.rb
|