lex-synapse 0.2.3 → 0.3.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 +20 -0
- data/CLAUDE.md +18 -6
- data/lex-synapse.gemspec +1 -1
- data/lib/legion/extensions/synapse/actors/propose.rb +31 -0
- data/lib/legion/extensions/synapse/client.rb +22 -0
- data/lib/legion/extensions/synapse/data/migrations/004_create_synapse_proposals.rb +24 -0
- data/lib/legion/extensions/synapse/data/models/synapse.rb +2 -0
- data/lib/legion/extensions/synapse/data/models/synapse_proposal.rb +26 -0
- data/lib/legion/extensions/synapse/helpers/proposals.rb +58 -0
- data/lib/legion/extensions/synapse/runners/evaluate.rb +15 -0
- data/lib/legion/extensions/synapse/runners/propose.rb +253 -0
- data/lib/legion/extensions/synapse/version.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 60a95b064e889f6594f48ea66c0c12e8360b4679ef9daf572e31560e304ea277
|
|
4
|
+
data.tar.gz: a96ea18996d7b3d87ee309cbba68f2258b34f40fa9a0a61068562f81f347177d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9faa202ea887493c0ab6b3b9df9323757ca00e1fca347b0669876241ef4733aa4f333c5153c8b0a46d59fd28d6ba5e5286f5e7d541dc47d21e65ea4a9b6f554a
|
|
7
|
+
data.tar.gz: 85411a3ec047e7d9e944b61d08c03ab3ee3868f58887b6e13c3e6ab46d1fcad3ef5a56ddf73d12cc8a32a77b4d1c2d34386caa9d2711950c464a818229fdb495
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-03-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Autonomous observation mode: proposal engine for AUTONOMOUS tier (confidence 0.8+)
|
|
7
|
+
- `synapse_proposals` table with migration 004
|
|
8
|
+
- `Runners::Propose` with reactive proposals (no-template, transform failure, pain correlation)
|
|
9
|
+
- `Runners::Propose` with proactive analysis (success rate degradation, payload drift)
|
|
10
|
+
- `Actors::Propose` periodic actor for proactive analysis (every 300s)
|
|
11
|
+
- `Helpers::Proposals` settings helper with configurable thresholds
|
|
12
|
+
- Proposal hook in `Runners::Evaluate` for autonomous synapses (gated by settings)
|
|
13
|
+
- Client `proposals(synapse_id:, status:)` query method
|
|
14
|
+
- Client `review_proposal(proposal_id:, status:)` for approving/rejecting proposals
|
|
15
|
+
- LLM-backed proposal generation via lex-transformer LLM engine
|
|
16
|
+
- Proposal deduplication within configurable window
|
|
17
|
+
- Integration specs for full proposal workflow
|
|
18
|
+
- Settings: `lex-synapse.proposals.*` for master switch, reactive/proactive toggles, max_per_run, LLM engine options, thresholds
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- `lex-transformer` dependency bumped to >= 0.3.0 (requires LLM engine with engine_options)
|
|
22
|
+
|
|
3
23
|
## [0.2.3] - 2026-03-19
|
|
4
24
|
|
|
5
25
|
### Fixed
|
data/CLAUDE.md
CHANGED
|
@@ -10,7 +10,7 @@ Cognitive routing layer that wraps task chain relationships with observation, le
|
|
|
10
10
|
|
|
11
11
|
**GitHub**: https://github.com/LegionIO/lex-synapse
|
|
12
12
|
**License**: MIT
|
|
13
|
-
**Version**: 0.
|
|
13
|
+
**Version**: 0.3.0
|
|
14
14
|
|
|
15
15
|
## Architecture
|
|
16
16
|
|
|
@@ -21,7 +21,8 @@ Legion::Extensions::Synapse
|
|
|
21
21
|
│ ├── Pain # Subscription — task.failed handler
|
|
22
22
|
│ ├── Crystallize # Every 300s — emergent synapse detection
|
|
23
23
|
│ ├── Homeostasis # Every 30s — spike/drought monitoring
|
|
24
|
-
│
|
|
24
|
+
│ ├── Decay # Every 3600s — idle confidence decay
|
|
25
|
+
│ └── Propose # Every 300s — proactive proposal analysis for AUTONOMOUS synapses
|
|
25
26
|
├── Runners/
|
|
26
27
|
│ ├── Evaluate # attention -> transform -> route -> record
|
|
27
28
|
│ ├── Pain # failure recording, confidence hit, auto-revert
|
|
@@ -32,7 +33,8 @@ Legion::Extensions::Synapse
|
|
|
32
33
|
│ ├── Dream # replay historical signals in simulation mode; replay/simulate
|
|
33
34
|
│ ├── GaiaReport # GAIA tick hook: report confidence and health per synapse
|
|
34
35
|
│ ├── Promote # Apollo integration: promote high-confidence synapse patterns to shared knowledge
|
|
35
|
-
│
|
|
36
|
+
│ ├── Retrieve # Apollo integration: retrieve relevant synapse patterns from shared knowledge
|
|
37
|
+
│ └── Propose # reactive (signal-driven) + proactive (periodic) proposal generation
|
|
36
38
|
├── Helpers/
|
|
37
39
|
│ ├── Confidence # scoring, adjustments, autonomy ranges, decay
|
|
38
40
|
│ ├── Homeostasis # spike/drought detection, baseline tracking
|
|
@@ -79,6 +81,16 @@ Legion::Extensions::Synapse
|
|
|
79
81
|
- **synapse_mutations**: Versioned change history with before/after JSON snapshots
|
|
80
82
|
- **synapse_signals**: Per-signal outcome records (attention pass, transform success, latency, downstream outcome)
|
|
81
83
|
|
|
84
|
+
## Autonomous Observation Mode (v0.3.0)
|
|
85
|
+
|
|
86
|
+
- **Proposal engine**: AUTONOMOUS tier (confidence 0.8+) generates proposals instead of executing autonomous actions
|
|
87
|
+
- **Reactive proposals**: on signal evaluation — no-template inference, transform failure fix, attention pain correlation
|
|
88
|
+
- **Proactive proposals**: periodic analysis — success rate degradation, payload drift detection
|
|
89
|
+
- **LLM-backed**: proposals call lex-transformer LLM engine for real output generation
|
|
90
|
+
- **Settings**: `lex-synapse.proposals.*` — enabled, reactive, proactive, max_per_run, llm_engine_options, thresholds
|
|
91
|
+
- **Data**: `synapse_proposals` table with status lifecycle (pending -> approved/rejected/applied/expired)
|
|
92
|
+
- **Client methods**: `proposals(synapse_id:, status:)`, `review_proposal(proposal_id:, status:)`
|
|
93
|
+
|
|
82
94
|
## GAIA / Apollo Integration (v0.2.2)
|
|
83
95
|
|
|
84
96
|
- **GaiaReport runner**: Called during the GAIA tick cycle to report per-synapse confidence and health metrics.
|
|
@@ -91,18 +103,18 @@ Legion::Extensions::Synapse
|
|
|
91
103
|
| Gem | Purpose |
|
|
92
104
|
|-----|---------|
|
|
93
105
|
| `lex-conditioner` >= 0.3.0 | Attention evaluation (condition rules) |
|
|
94
|
-
| `lex-transformer` >= 0.
|
|
106
|
+
| `lex-transformer` >= 0.3.0 | Payload transformation (template engines) |
|
|
95
107
|
| `legion-data` | Required — database persistence via Sequel |
|
|
96
108
|
|
|
97
109
|
## Testing
|
|
98
110
|
|
|
99
111
|
```bash
|
|
100
112
|
bundle install
|
|
101
|
-
bundle exec rspec #
|
|
113
|
+
bundle exec rspec # 366 specs, 0 failures
|
|
102
114
|
bundle exec rubocop # 0 offenses
|
|
103
115
|
```
|
|
104
116
|
|
|
105
|
-
|
|
117
|
+
366 specs, 95%+ coverage. Uses in-memory SQLite for model/runner tests.
|
|
106
118
|
|
|
107
119
|
---
|
|
108
120
|
|
data/lex-synapse.gemspec
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 Propose < Legion::Extensions::Actors::Every
|
|
8
|
+
def runner_function
|
|
9
|
+
'propose_proactive'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def time
|
|
13
|
+
300
|
|
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
|
|
@@ -10,6 +10,9 @@ require_relative 'runners/gaia_report'
|
|
|
10
10
|
require_relative 'runners/dream'
|
|
11
11
|
require_relative 'runners/promote'
|
|
12
12
|
require_relative 'runners/retrieve'
|
|
13
|
+
require_relative 'runners/propose'
|
|
14
|
+
require_relative 'data/models/synapse_proposal'
|
|
15
|
+
require_relative 'helpers/proposals'
|
|
13
16
|
|
|
14
17
|
module Legion
|
|
15
18
|
module Extensions
|
|
@@ -25,6 +28,7 @@ module Legion
|
|
|
25
28
|
include Runners::Dream
|
|
26
29
|
include Runners::Promote
|
|
27
30
|
include Runners::Retrieve
|
|
31
|
+
include Runners::Propose
|
|
28
32
|
|
|
29
33
|
attr_reader :conditioner_client, :transformer_client
|
|
30
34
|
|
|
@@ -57,6 +61,24 @@ module Legion
|
|
|
57
61
|
status: origin == 'emergent' ? 'observing' : 'active'
|
|
58
62
|
)
|
|
59
63
|
end
|
|
64
|
+
|
|
65
|
+
def proposals(synapse_id:, status: nil)
|
|
66
|
+
Data::Model.define_synapse_proposal_model
|
|
67
|
+
dataset = Data::Model::SynapseProposal.where(synapse_id: synapse_id)
|
|
68
|
+
dataset = dataset.where(status: status) if status
|
|
69
|
+
dataset.order(Sequel.desc(:id)).all
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def review_proposal(proposal_id:, status:)
|
|
73
|
+
Data::Model.define_synapse_proposal_model
|
|
74
|
+
return { success: false, error: "invalid status: #{status}" } unless Helpers::Proposals::VALID_STATUSES.include?(status)
|
|
75
|
+
|
|
76
|
+
proposal = Data::Model::SynapseProposal[proposal_id]
|
|
77
|
+
return { success: false, error: 'proposal not found' } unless proposal
|
|
78
|
+
|
|
79
|
+
proposal.update(status: status, reviewed_at: Time.now)
|
|
80
|
+
{ success: true, proposal_id: proposal_id, status: status }
|
|
81
|
+
end
|
|
60
82
|
end
|
|
61
83
|
end
|
|
62
84
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
create_table(:synapse_proposals) do
|
|
6
|
+
primary_key :id
|
|
7
|
+
foreign_key :synapse_id, :synapses, null: false, index: true
|
|
8
|
+
Integer :signal_id
|
|
9
|
+
String :proposal_type, null: false, size: 50
|
|
10
|
+
String :trigger, null: false, size: 50
|
|
11
|
+
String :inputs, text: true
|
|
12
|
+
String :output, text: true
|
|
13
|
+
String :rationale, text: true
|
|
14
|
+
String :status, default: 'pending', size: 50
|
|
15
|
+
Float :estimated_confidence_impact
|
|
16
|
+
DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
17
|
+
DateTime :reviewed_at
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
down do
|
|
22
|
+
drop_table :synapse_proposals
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -17,6 +17,8 @@ module Legion
|
|
|
17
17
|
key: :synapse_id
|
|
18
18
|
one_to_many :signals, class: 'Legion::Extensions::Synapse::Data::Model::SynapseSignal',
|
|
19
19
|
key: :synapse_id
|
|
20
|
+
one_to_many :proposals, class: 'Legion::Extensions::Synapse::Data::Model::SynapseProposal',
|
|
21
|
+
key: :synapse_id
|
|
20
22
|
end
|
|
21
23
|
klass.set_primary_key :id
|
|
22
24
|
const_set(:Synapse, klass)
|
|
@@ -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_proposal_model
|
|
9
|
+
return if const_defined?(:SynapseProposal, 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_proposals)
|
|
14
|
+
|
|
15
|
+
klass = Class.new(Sequel::Model(:synapse_proposals)) do
|
|
16
|
+
many_to_one :synapse, class: 'Legion::Extensions::Synapse::Data::Model::Synapse',
|
|
17
|
+
key: :synapse_id
|
|
18
|
+
end
|
|
19
|
+
klass.set_primary_key :id
|
|
20
|
+
const_set(:SynapseProposal, klass)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Synapse
|
|
6
|
+
module Helpers
|
|
7
|
+
module Proposals
|
|
8
|
+
VALID_PROPOSAL_TYPES = %w[llm_transform attention_mutation transform_mutation route_change].freeze
|
|
9
|
+
VALID_TRIGGERS = %w[reactive proactive].freeze
|
|
10
|
+
VALID_STATUSES = %w[pending approved rejected applied expired].freeze
|
|
11
|
+
|
|
12
|
+
DEFAULT_SETTINGS = {
|
|
13
|
+
enabled: true,
|
|
14
|
+
reactive: true,
|
|
15
|
+
proactive: true,
|
|
16
|
+
proactive_interval: 300,
|
|
17
|
+
max_per_run: 3,
|
|
18
|
+
llm_engine_options: { temperature: 0.3, max_tokens: 1024 },
|
|
19
|
+
success_rate_threshold: 0.8,
|
|
20
|
+
payload_drift_threshold: 0.2,
|
|
21
|
+
dedup_window_hours: 24
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
def settings
|
|
26
|
+
raw = Legion::Settings.dig('lex-synapse', 'proposals')
|
|
27
|
+
return DEFAULT_SETTINGS.dup unless raw.is_a?(Hash)
|
|
28
|
+
|
|
29
|
+
merged = DEFAULT_SETTINGS.dup
|
|
30
|
+
raw.each { |k, v| merged[k.to_sym] = v unless v.nil? }
|
|
31
|
+
merged
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def enabled?
|
|
35
|
+
settings[:enabled] == true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def reactive?
|
|
39
|
+
s = settings
|
|
40
|
+
s[:enabled] == true && s[:reactive] == true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def proactive?
|
|
44
|
+
s = settings
|
|
45
|
+
s[:enabled] == true && s[:proactive] == true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def llm_engine_options
|
|
49
|
+
s = settings
|
|
50
|
+
opts = s[:llm_engine_options]
|
|
51
|
+
opts.is_a?(Hash) ? opts.transform_keys(&:to_sym) : DEFAULT_SETTINGS[:llm_engine_options].dup
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../helpers/confidence'
|
|
4
|
+
require_relative '../helpers/proposals'
|
|
4
5
|
require_relative '../data/models/synapse'
|
|
5
6
|
require_relative '../data/models/synapse_mutation'
|
|
6
7
|
require_relative '../data/models/synapse_signal'
|
|
8
|
+
require_relative 'propose'
|
|
7
9
|
|
|
8
10
|
module Legion
|
|
9
11
|
module Extensions
|
|
10
12
|
module Synapse
|
|
11
13
|
module Runners
|
|
12
14
|
module Evaluate
|
|
15
|
+
include Propose
|
|
16
|
+
|
|
13
17
|
def evaluate(synapse_id:, payload: {}, conditioner_client: nil, transformer_client: nil)
|
|
14
18
|
Data::Model.define_synapse_model
|
|
15
19
|
Data::Model.define_synapse_signal_model
|
|
20
|
+
Data::Model.define_synapse_proposal_model
|
|
16
21
|
synapse = Data::Model::Synapse[synapse_id]
|
|
17
22
|
return { success: false, error: 'synapse not found' } unless synapse
|
|
18
23
|
return { success: false, error: 'synapse not active' } unless Helpers::Confidence::EVALUABLE_STATUSES.include?(synapse.status)
|
|
@@ -40,6 +45,16 @@ module Legion
|
|
|
40
45
|
new_confidence = Helpers::Confidence.adjust(synapse.confidence, event)
|
|
41
46
|
synapse.update(confidence: new_confidence)
|
|
42
47
|
|
|
48
|
+
# Step 5: Generate proposals if autonomous
|
|
49
|
+
if Helpers::Confidence.can_self_modify?(new_confidence) && Helpers::Proposals.reactive?
|
|
50
|
+
signal_record = Data::Model::SynapseSignal.where(synapse_id: synapse.id).order(Sequel.desc(:id)).first
|
|
51
|
+
propose_reactive(
|
|
52
|
+
synapse: synapse, payload: payload, signal_id: signal_record&.id,
|
|
53
|
+
attention_result: attention_result, transform_result: transform_result,
|
|
54
|
+
transformer_client: transformer_client
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
43
58
|
{
|
|
44
59
|
success: transform_result[:success],
|
|
45
60
|
mode: mode,
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/confidence'
|
|
4
|
+
require_relative '../helpers/proposals'
|
|
5
|
+
require_relative '../data/models/synapse'
|
|
6
|
+
require_relative '../data/models/synapse_signal'
|
|
7
|
+
require_relative '../data/models/synapse_proposal'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module Synapse
|
|
12
|
+
module Runners
|
|
13
|
+
module Propose
|
|
14
|
+
PAIN_CORRELATION_THRESHOLD = 3
|
|
15
|
+
|
|
16
|
+
def propose_reactive(synapse:, payload:, signal_id:, attention_result:, transform_result:,
|
|
17
|
+
transformer_client: nil)
|
|
18
|
+
Data::Model.define_synapse_proposal_model
|
|
19
|
+
return { proposals: [] } unless Helpers::Proposals.reactive?
|
|
20
|
+
return { proposals: [] } unless transformer_client
|
|
21
|
+
|
|
22
|
+
proposals = []
|
|
23
|
+
|
|
24
|
+
# Trigger 1: No transform template exists
|
|
25
|
+
proposals << propose_llm_transform(synapse, payload, signal_id, transformer_client) if synapse.transform.nil? || synapse.transform.to_s.strip.empty?
|
|
26
|
+
|
|
27
|
+
# Trigger 2: Transform failed
|
|
28
|
+
if transform_result[:success] == false && synapse.transform && !synapse.transform.to_s.strip.empty?
|
|
29
|
+
proposals << propose_transform_fix(synapse, payload, signal_id, transform_result, transformer_client)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Trigger 3: Pain correlation — attention passed but recent downstream failures
|
|
33
|
+
proposals << propose_attention_adjustment(synapse, payload, signal_id, transformer_client) if attention_result[:passed] && pain_pattern?(synapse)
|
|
34
|
+
|
|
35
|
+
{ proposals: proposals.compact }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def propose_proactive
|
|
39
|
+
Data::Model.define_synapse_model
|
|
40
|
+
Data::Model.define_synapse_proposal_model
|
|
41
|
+
Data::Model.define_synapse_signal_model
|
|
42
|
+
return { proposals: [] } unless Helpers::Proposals.proactive?
|
|
43
|
+
|
|
44
|
+
settings = Helpers::Proposals.settings
|
|
45
|
+
max_per_run = settings[:max_per_run] || 3
|
|
46
|
+
all_proposals = []
|
|
47
|
+
|
|
48
|
+
Data::Model::Synapse.where(status: 'active').all.each do |synapse|
|
|
49
|
+
next unless Helpers::Confidence.can_self_modify?(synapse.confidence)
|
|
50
|
+
|
|
51
|
+
count = 0
|
|
52
|
+
proposal = analyze_success_rate(synapse, settings)
|
|
53
|
+
if proposal
|
|
54
|
+
all_proposals << proposal
|
|
55
|
+
count += 1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if count < max_per_run
|
|
59
|
+
proposal = analyze_payload_drift(synapse, settings)
|
|
60
|
+
if proposal
|
|
61
|
+
all_proposals << proposal
|
|
62
|
+
count += 1
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if count < max_per_run
|
|
67
|
+
proposal = analyze_routing(synapse)
|
|
68
|
+
all_proposals << proposal if proposal
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
{ proposals: all_proposals }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def propose_llm_transform(synapse, payload, signal_id, transformer_client)
|
|
78
|
+
source_schema = infer_schema(payload)
|
|
79
|
+
target_schema = lookup_target_schema(synapse)
|
|
80
|
+
prompt = build_transform_prompt(source_schema, target_schema)
|
|
81
|
+
|
|
82
|
+
llm_result = call_llm(transformer_client, prompt)
|
|
83
|
+
create_proposal(
|
|
84
|
+
synapse: synapse, signal_id: signal_id, proposal_type: 'llm_transform',
|
|
85
|
+
trigger: 'reactive',
|
|
86
|
+
inputs: Legion::JSON.dump({ source_schema: source_schema, target_schema: target_schema }),
|
|
87
|
+
output: llm_result[:output],
|
|
88
|
+
rationale: 'no transform template exists for this synapse'
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def propose_transform_fix(synapse, payload, signal_id, transform_result, transformer_client)
|
|
93
|
+
source_schema = infer_schema(payload)
|
|
94
|
+
current_transform = synapse.transform
|
|
95
|
+
errors = transform_result[:error]
|
|
96
|
+
prompt = build_fix_prompt(current_transform, source_schema, errors)
|
|
97
|
+
|
|
98
|
+
llm_result = call_llm(transformer_client, prompt)
|
|
99
|
+
create_proposal(
|
|
100
|
+
synapse: synapse, signal_id: signal_id, proposal_type: 'transform_mutation',
|
|
101
|
+
trigger: 'reactive',
|
|
102
|
+
inputs: Legion::JSON.dump({ current_transform: current_transform, errors: errors, payload_schema: source_schema }),
|
|
103
|
+
output: llm_result[:output],
|
|
104
|
+
rationale: "transform failed: #{Array(errors).first}"
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def propose_attention_adjustment(synapse, payload, signal_id, transformer_client)
|
|
109
|
+
recent_failures = recent_failed_signals(synapse, 10)
|
|
110
|
+
prompt = build_attention_prompt(synapse.attention, payload, recent_failures)
|
|
111
|
+
|
|
112
|
+
llm_result = call_llm(transformer_client, prompt)
|
|
113
|
+
create_proposal(
|
|
114
|
+
synapse: synapse, signal_id: signal_id, proposal_type: 'attention_mutation',
|
|
115
|
+
trigger: 'reactive',
|
|
116
|
+
inputs: Legion::JSON.dump({ current_attention: synapse.attention, recent_failures: recent_failures.size }),
|
|
117
|
+
output: llm_result[:output],
|
|
118
|
+
rationale: "#{recent_failures.size} recent downstream failures despite attention passing"
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def analyze_success_rate(synapse, settings)
|
|
123
|
+
threshold = settings[:success_rate_threshold] || 0.8
|
|
124
|
+
signals = Data::Model::SynapseSignal.where(synapse_id: synapse.id).order(Sequel.desc(:id)).limit(100).all
|
|
125
|
+
return nil if signals.size < 10
|
|
126
|
+
|
|
127
|
+
success_count = signals.count(&:transform_success)
|
|
128
|
+
rate = success_count.to_f / signals.size
|
|
129
|
+
return nil if rate >= threshold
|
|
130
|
+
return nil if dedup_exists?(synapse.id, 'transform_mutation', 'proactive', settings)
|
|
131
|
+
|
|
132
|
+
create_proposal(
|
|
133
|
+
synapse: synapse, signal_id: nil, proposal_type: 'transform_mutation',
|
|
134
|
+
trigger: 'proactive',
|
|
135
|
+
inputs: Legion::JSON.dump({ success_rate: rate.round(3), sample_size: signals.size, threshold: threshold }),
|
|
136
|
+
output: nil,
|
|
137
|
+
rationale: "success rate #{(rate * 100).round(1)}% below threshold #{(threshold * 100).round(1)}%"
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def analyze_payload_drift(synapse, settings)
|
|
142
|
+
return nil if synapse.transform.nil? || synapse.transform.to_s.strip.empty?
|
|
143
|
+
|
|
144
|
+
drift_threshold = settings[:payload_drift_threshold] || 0.2
|
|
145
|
+
signals = Data::Model::SynapseSignal.where(synapse_id: synapse.id).order(Sequel.desc(:id)).limit(50).all
|
|
146
|
+
return nil if signals.size < 10
|
|
147
|
+
|
|
148
|
+
failed = signals.count { |s| !s.transform_success }
|
|
149
|
+
drift_rate = failed.to_f / signals.size
|
|
150
|
+
return nil if drift_rate < drift_threshold
|
|
151
|
+
return nil if dedup_exists?(synapse.id, 'transform_mutation', 'proactive', settings)
|
|
152
|
+
|
|
153
|
+
create_proposal(
|
|
154
|
+
synapse: synapse, signal_id: nil, proposal_type: 'transform_mutation',
|
|
155
|
+
trigger: 'proactive',
|
|
156
|
+
inputs: Legion::JSON.dump({ drift_rate: drift_rate.round(3), sample_size: signals.size }),
|
|
157
|
+
output: nil,
|
|
158
|
+
rationale: "payload drift detected: #{(drift_rate * 100).round(1)}% transform failures in recent signals"
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def analyze_routing(synapse)
|
|
163
|
+
return nil if dedup_exists?(synapse.id, 'route_change', 'proactive', Helpers::Proposals.settings)
|
|
164
|
+
|
|
165
|
+
signals = Data::Model::SynapseSignal.where(synapse_id: synapse.id).order(Sequel.desc(:id)).limit(50).all
|
|
166
|
+
return nil if signals.size < 10
|
|
167
|
+
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def pain_pattern?(synapse)
|
|
172
|
+
recent = recent_failed_signals(synapse, 20)
|
|
173
|
+
recent.size >= PAIN_CORRELATION_THRESHOLD
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def recent_failed_signals(synapse, limit)
|
|
177
|
+
Data::Model::SynapseSignal.where(
|
|
178
|
+
synapse_id: synapse.id, downstream_outcome: 'failed'
|
|
179
|
+
).order(Sequel.desc(:id)).limit(limit).all
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def dedup_exists?(synapse_id, proposal_type, trigger, settings)
|
|
183
|
+
window = settings[:dedup_window_hours] || 24
|
|
184
|
+
cutoff = Time.now - (window * 3600)
|
|
185
|
+
Data::Model::SynapseProposal.where(
|
|
186
|
+
synapse_id: synapse_id, proposal_type: proposal_type,
|
|
187
|
+
trigger: trigger, status: 'pending'
|
|
188
|
+
).where(Sequel.lit('created_at >= ?', cutoff)).any?
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def create_proposal(synapse:, signal_id:, proposal_type:, trigger:, inputs:, output:, rationale:)
|
|
192
|
+
Data::Model::SynapseProposal.create(
|
|
193
|
+
synapse_id: synapse.id,
|
|
194
|
+
signal_id: signal_id,
|
|
195
|
+
proposal_type: proposal_type,
|
|
196
|
+
trigger: trigger,
|
|
197
|
+
inputs: inputs,
|
|
198
|
+
output: output,
|
|
199
|
+
rationale: rationale,
|
|
200
|
+
status: 'pending'
|
|
201
|
+
)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def call_llm(transformer_client, prompt)
|
|
205
|
+
return { output: nil } unless transformer_client
|
|
206
|
+
|
|
207
|
+
engine_options = Helpers::Proposals.llm_engine_options
|
|
208
|
+
result = transformer_client.transform(
|
|
209
|
+
transformation: prompt, payload: {}, engine: :llm, engine_options: engine_options
|
|
210
|
+
)
|
|
211
|
+
{ output: result[:success] ? Legion::JSON.dump(result[:result]) : nil }
|
|
212
|
+
rescue StandardError => e
|
|
213
|
+
Legion::Logging.warn("Proposal LLM call failed: #{e.message}")
|
|
214
|
+
{ output: nil }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def infer_schema(payload)
|
|
218
|
+
return {} unless payload.is_a?(Hash)
|
|
219
|
+
|
|
220
|
+
payload.transform_values { |v| v.class.name }
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def lookup_target_schema(synapse)
|
|
224
|
+
return {} unless synapse.target_function_id
|
|
225
|
+
return {} unless defined?(Legion::Extensions::Lex)
|
|
226
|
+
|
|
227
|
+
{}
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def build_transform_prompt(source_schema, target_schema)
|
|
231
|
+
"Given a payload with schema: #{Legion::JSON.dump(source_schema)}, " \
|
|
232
|
+
"generate a JSON transform template that maps it to target schema: #{Legion::JSON.dump(target_schema)}. " \
|
|
233
|
+
'Return only the JSON template string, no explanation.'
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def build_fix_prompt(current_transform, source_schema, errors)
|
|
237
|
+
"The current transform template is: #{current_transform}. " \
|
|
238
|
+
"It failed with errors: #{Array(errors).join(', ')}. " \
|
|
239
|
+
"The payload schema is: #{Legion::JSON.dump(source_schema)}. " \
|
|
240
|
+
'Suggest a corrected transform template. Return only the JSON template string.'
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def build_attention_prompt(current_attention, payload, recent_failures)
|
|
244
|
+
"The current attention rules are: #{current_attention}. " \
|
|
245
|
+
"Recent payload example: #{Legion::JSON.dump(payload)}. " \
|
|
246
|
+
"There have been #{recent_failures.size} downstream failures despite attention passing. " \
|
|
247
|
+
'Suggest refined attention rules as a JSON condition object. Return only JSON.'
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
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.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.3.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 0.3.0
|
|
40
40
|
description: Attention, transformation, and routing with confidence scoring, pain
|
|
41
41
|
signals, homeostasis, and self-governance
|
|
42
42
|
email:
|
|
@@ -61,15 +61,19 @@ files:
|
|
|
61
61
|
- lib/legion/extensions/synapse/actors/evaluate.rb
|
|
62
62
|
- lib/legion/extensions/synapse/actors/homeostasis.rb
|
|
63
63
|
- lib/legion/extensions/synapse/actors/pain.rb
|
|
64
|
+
- lib/legion/extensions/synapse/actors/propose.rb
|
|
64
65
|
- lib/legion/extensions/synapse/client.rb
|
|
65
66
|
- lib/legion/extensions/synapse/data/migrations/001_create_synapses.rb
|
|
66
67
|
- lib/legion/extensions/synapse/data/migrations/002_create_synapse_mutations.rb
|
|
67
68
|
- lib/legion/extensions/synapse/data/migrations/003_create_synapse_signals.rb
|
|
69
|
+
- lib/legion/extensions/synapse/data/migrations/004_create_synapse_proposals.rb
|
|
68
70
|
- lib/legion/extensions/synapse/data/models/synapse.rb
|
|
69
71
|
- lib/legion/extensions/synapse/data/models/synapse_mutation.rb
|
|
72
|
+
- lib/legion/extensions/synapse/data/models/synapse_proposal.rb
|
|
70
73
|
- lib/legion/extensions/synapse/data/models/synapse_signal.rb
|
|
71
74
|
- lib/legion/extensions/synapse/helpers/confidence.rb
|
|
72
75
|
- lib/legion/extensions/synapse/helpers/homeostasis.rb
|
|
76
|
+
- lib/legion/extensions/synapse/helpers/proposals.rb
|
|
73
77
|
- lib/legion/extensions/synapse/helpers/relationship_wrapper.rb
|
|
74
78
|
- lib/legion/extensions/synapse/runners/crystallize.rb
|
|
75
79
|
- lib/legion/extensions/synapse/runners/dream.rb
|
|
@@ -78,6 +82,7 @@ files:
|
|
|
78
82
|
- lib/legion/extensions/synapse/runners/mutate.rb
|
|
79
83
|
- lib/legion/extensions/synapse/runners/pain.rb
|
|
80
84
|
- lib/legion/extensions/synapse/runners/promote.rb
|
|
85
|
+
- lib/legion/extensions/synapse/runners/propose.rb
|
|
81
86
|
- lib/legion/extensions/synapse/runners/report.rb
|
|
82
87
|
- lib/legion/extensions/synapse/runners/retrieve.rb
|
|
83
88
|
- lib/legion/extensions/synapse/runners/revert.rb
|