lex-agentic-social 0.1.16 → 0.1.17
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 +5 -0
- data/lib/legion/extensions/agentic/social/calibration/runners/calibration.rb +4 -1
- data/lib/legion/extensions/agentic/social/conflict/helpers/llm_enhancer.rb +8 -3
- data/lib/legion/extensions/agentic/social/moral_reasoning/helpers/llm_enhancer.rb +8 -3
- data/lib/legion/extensions/agentic/social/version.rb +1 -1
- data/spec/legion/extensions/agentic/social/calibration/runners/calibration_spec.rb +48 -0
- data/spec/legion/extensions/agentic/social/conflict/helpers/llm_enhancer_spec.rb +88 -0
- data/spec/legion/extensions/agentic/social/moral_reasoning/helpers/llm_enhancer_spec.rb +104 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2fc82cf70d0df9c909097bbed4d53fc58e64e2ca8904a49c6d7abf030fc3b2c6
|
|
4
|
+
data.tar.gz: 41e0f3966d50d002ccd5cf3a96d488b7bb1d2208cc4f36b931a5fd311786c80a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e013c357c5281df840b02a4ec06d16fee47d63af742b97ae1a6fe132612b66f6c8464b0316b1d564e6cc3927da03b8b21629500fc52685206af0ef28649865f6
|
|
7
|
+
data.tar.gz: 01a2eb7ebda54716bfb68542f22622aedf96525b98809a84d758f1fd2c8fc44d7eb6e5a02cfbf1354da466cbfebe786b99fed33212d537df1bb9cc1963bb7eb2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.17] - 2026-05-15
|
|
4
|
+
### Fixed
|
|
5
|
+
- Social LLM enhancers now send explicit system and user messages to native `Legion::LLM.chat` dispatch instead of opening legacy nil-input chat sessions.
|
|
6
|
+
- Calibration LLM preference upsert now passes `access_scope: 'private'` and `identity_principal_id: nil` to prevent daemon process identity injection on personal data writes.
|
|
7
|
+
|
|
3
8
|
## [0.1.16] - 2026-05-07
|
|
4
9
|
### Fixed
|
|
5
10
|
- Calibration preference storage now handles symbol-keyed parsed LLM preferences without dropping domain or value.
|
|
@@ -204,7 +204,10 @@ module Legion
|
|
|
204
204
|
'confidence' => confidence
|
|
205
205
|
})
|
|
206
206
|
tags = base_tags + ["preference:#{domain}"]
|
|
207
|
-
Legion::Apollo::Local.upsert(content: content, tags: tags,
|
|
207
|
+
Legion::Apollo::Local.upsert(content: content, tags: tags,
|
|
208
|
+
confidence: confidence,
|
|
209
|
+
access_scope: 'private',
|
|
210
|
+
identity_principal_id: nil)
|
|
208
211
|
end
|
|
209
212
|
end
|
|
210
213
|
end
|
|
@@ -57,9 +57,14 @@ module Legion
|
|
|
57
57
|
content = response&.message&.dig(:content)
|
|
58
58
|
::Struct.new(:content).new(content) if content
|
|
59
59
|
else
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
response = Legion::LLM.chat(
|
|
61
|
+
message: [
|
|
62
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
63
|
+
{ role: 'user', content: prompt }
|
|
64
|
+
],
|
|
65
|
+
caller: { extension: 'lex-agentic-social', mode: :conflict }
|
|
66
|
+
)
|
|
67
|
+
response.respond_to?(:content) ? response : response.ask(prompt)
|
|
63
68
|
end
|
|
64
69
|
end
|
|
65
70
|
private_class_method :llm_ask
|
|
@@ -50,9 +50,14 @@ module Legion
|
|
|
50
50
|
content = response&.message&.dig(:content)
|
|
51
51
|
::Struct.new(:content).new(content) if content
|
|
52
52
|
else
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
response = Legion::LLM.chat(
|
|
54
|
+
message: [
|
|
55
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
56
|
+
{ role: 'user', content: prompt }
|
|
57
|
+
],
|
|
58
|
+
caller: { extension: 'lex-agentic-social', mode: :moral_reasoning }
|
|
59
|
+
)
|
|
60
|
+
response.respond_to?(:content) ? response : response.ask(prompt)
|
|
56
61
|
end
|
|
57
62
|
end
|
|
58
63
|
private_class_method :llm_ask
|
|
@@ -200,4 +200,52 @@ RSpec.describe Legion::Extensions::Agentic::Social::Calibration::Runners::Calibr
|
|
|
200
200
|
expect(result[:results]).to have_key(:promotion)
|
|
201
201
|
end
|
|
202
202
|
end
|
|
203
|
+
|
|
204
|
+
describe '#store_llm_preferences' do
|
|
205
|
+
let(:preferences) do
|
|
206
|
+
[{ 'domain' => 'communication', 'value' => 'direct', 'confidence' => 0.8 }]
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
before do
|
|
210
|
+
mock_local = double('apollo_local')
|
|
211
|
+
stub_const('Legion::Apollo', Module.new)
|
|
212
|
+
stub_const('Legion::Apollo::Local', mock_local)
|
|
213
|
+
allow(mock_local).to receive(:started?).and_return(true)
|
|
214
|
+
allow(mock_local).to receive(:upsert).and_return({ success: true })
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'passes access_scope: private to Apollo::Local.upsert' do
|
|
218
|
+
mock_local = Legion::Apollo::Local
|
|
219
|
+
client.send(:store_llm_preferences, preferences)
|
|
220
|
+
expect(mock_local).to have_received(:upsert).with(
|
|
221
|
+
hash_including(access_scope: 'private')
|
|
222
|
+
)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it 'passes content and tags to Apollo::Local.upsert' do
|
|
226
|
+
mock_local = Legion::Apollo::Local
|
|
227
|
+
client.send(:store_llm_preferences, preferences)
|
|
228
|
+
expect(mock_local).to have_received(:upsert).with(
|
|
229
|
+
hash_including(
|
|
230
|
+
content: a_string_including('direct'),
|
|
231
|
+
tags: array_including('preference', 'preference:communication')
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it 'does not inject process identity as the owner' do
|
|
237
|
+
stub_const('Legion::Identity::Process', Module.new do
|
|
238
|
+
extend self
|
|
239
|
+
|
|
240
|
+
define_method(:identity_hash) do
|
|
241
|
+
{ canonical_name: 'daemon', db_principal_id: 999, db_identity_id: 888 }
|
|
242
|
+
end
|
|
243
|
+
end)
|
|
244
|
+
mock_local = Legion::Apollo::Local
|
|
245
|
+
client.send(:store_llm_preferences, preferences)
|
|
246
|
+
expect(mock_local).to have_received(:upsert).with(
|
|
247
|
+
hash_including(identity_principal_id: nil)
|
|
248
|
+
)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
203
251
|
end
|
|
@@ -112,6 +112,53 @@ RSpec.describe Legion::Extensions::Agentic::Social::Conflict::Helpers::LlmEnhanc
|
|
|
112
112
|
expect(result).to be_nil
|
|
113
113
|
end
|
|
114
114
|
end
|
|
115
|
+
|
|
116
|
+
context 'when LLM returns a native content response (direct dispatch path)' do
|
|
117
|
+
let(:native_response) do
|
|
118
|
+
double(content: "OUTCOME: resolved\nNOTES: Native dispatch resolved the conflict directly.")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
before do
|
|
122
|
+
allow(Legion::LLM).to receive(:chat).and_return(native_response)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'calls Legion::LLM.chat with system and user messages' do
|
|
126
|
+
expect(Legion::LLM).to receive(:chat).with(
|
|
127
|
+
hash_including(
|
|
128
|
+
message: array_including(
|
|
129
|
+
hash_including(role: 'system'),
|
|
130
|
+
hash_including(role: 'user')
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
).and_return(native_response)
|
|
134
|
+
described_class.suggest_resolution(
|
|
135
|
+
description: 'Agent and human disagree',
|
|
136
|
+
severity: :medium,
|
|
137
|
+
exchanges: []
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'calls Legion::LLM.chat with caller metadata' do
|
|
142
|
+
expect(Legion::LLM).to receive(:chat).with(
|
|
143
|
+
hash_including(caller: { extension: 'lex-agentic-social', mode: :conflict })
|
|
144
|
+
).and_return(native_response)
|
|
145
|
+
described_class.suggest_resolution(
|
|
146
|
+
description: 'Agent and human disagree',
|
|
147
|
+
severity: :medium,
|
|
148
|
+
exchanges: []
|
|
149
|
+
)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'uses the content directly without calling ask' do
|
|
153
|
+
expect(native_response).not_to receive(:ask)
|
|
154
|
+
result = described_class.suggest_resolution(
|
|
155
|
+
description: 'Agent and human disagree',
|
|
156
|
+
severity: :medium,
|
|
157
|
+
exchanges: []
|
|
158
|
+
)
|
|
159
|
+
expect(result[:suggested_outcome]).to eq(:resolved)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
115
162
|
end
|
|
116
163
|
|
|
117
164
|
describe '.analyze_stale_conflict' do
|
|
@@ -185,5 +232,46 @@ RSpec.describe Legion::Extensions::Agentic::Social::Conflict::Helpers::LlmEnhanc
|
|
|
185
232
|
expect(result).to be_nil
|
|
186
233
|
end
|
|
187
234
|
end
|
|
235
|
+
|
|
236
|
+
context 'when LLM returns a native content response (direct dispatch path)' do
|
|
237
|
+
let(:native_response) do
|
|
238
|
+
double(content: "RECOMMENDATION: escalate\nANALYSIS: Native dispatch recommends escalation.")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
before do
|
|
242
|
+
allow(Legion::LLM).to receive(:chat).and_return(native_response)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it 'calls Legion::LLM.chat with system and user messages' do
|
|
246
|
+
expect(Legion::LLM).to receive(:chat).with(
|
|
247
|
+
hash_including(
|
|
248
|
+
message: array_including(
|
|
249
|
+
hash_including(role: 'system'),
|
|
250
|
+
hash_including(role: 'user')
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
).and_return(native_response)
|
|
254
|
+
described_class.analyze_stale_conflict(
|
|
255
|
+
description: 'Stale safety concern', severity: :high, age_hours: 40.0, exchange_count: 1
|
|
256
|
+
)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it 'calls Legion::LLM.chat with caller metadata' do
|
|
260
|
+
expect(Legion::LLM).to receive(:chat).with(
|
|
261
|
+
hash_including(caller: { extension: 'lex-agentic-social', mode: :conflict })
|
|
262
|
+
).and_return(native_response)
|
|
263
|
+
described_class.analyze_stale_conflict(
|
|
264
|
+
description: 'Stale safety concern', severity: :high, age_hours: 40.0, exchange_count: 1
|
|
265
|
+
)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it 'uses the content directly without calling ask' do
|
|
269
|
+
expect(native_response).not_to receive(:ask)
|
|
270
|
+
result = described_class.analyze_stale_conflict(
|
|
271
|
+
description: 'Stale safety concern', severity: :high, age_hours: 40.0, exchange_count: 1
|
|
272
|
+
)
|
|
273
|
+
expect(result[:recommendation]).to eq(:escalate)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
188
276
|
end
|
|
189
277
|
end
|
|
@@ -150,6 +150,57 @@ RSpec.describe Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Llm
|
|
|
150
150
|
expect(result).to be_nil
|
|
151
151
|
end
|
|
152
152
|
end
|
|
153
|
+
|
|
154
|
+
context 'when LLM returns a native content response (direct dispatch path)' do
|
|
155
|
+
let(:native_response) do
|
|
156
|
+
content = <<~TEXT
|
|
157
|
+
REASONING: Native dispatch evaluated this action as broadly positive.
|
|
158
|
+
IMPACT: care=0.4 | fairness=0.3 | loyalty=0.1 | authority=0.0 | sanctity=0.2 | liberty=0.1
|
|
159
|
+
TEXT
|
|
160
|
+
double('native_response', content: content)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
before do
|
|
164
|
+
allow(Legion::LLM).to receive(:chat).and_return(native_response)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it 'calls Legion::LLM.chat with system and user messages' do
|
|
168
|
+
expect(Legion::LLM).to receive(:chat).with(
|
|
169
|
+
hash_including(
|
|
170
|
+
message: array_including(
|
|
171
|
+
hash_including(role: 'system'),
|
|
172
|
+
hash_including(role: 'user')
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
).and_return(native_response)
|
|
176
|
+
enhancer.evaluate_action(
|
|
177
|
+
action: :help_stranger,
|
|
178
|
+
description: 'Helping someone in need',
|
|
179
|
+
foundations: foundations
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it 'calls Legion::LLM.chat with caller metadata' do
|
|
184
|
+
expect(Legion::LLM).to receive(:chat).with(
|
|
185
|
+
hash_including(caller: { extension: 'lex-agentic-social', mode: :moral_reasoning })
|
|
186
|
+
).and_return(native_response)
|
|
187
|
+
enhancer.evaluate_action(
|
|
188
|
+
action: :help_stranger,
|
|
189
|
+
description: 'Helping someone in need',
|
|
190
|
+
foundations: foundations
|
|
191
|
+
)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it 'uses the content directly without calling ask' do
|
|
195
|
+
expect(native_response).not_to receive(:ask)
|
|
196
|
+
result = enhancer.evaluate_action(
|
|
197
|
+
action: :help_stranger,
|
|
198
|
+
description: 'Helping someone in need',
|
|
199
|
+
foundations: foundations
|
|
200
|
+
)
|
|
201
|
+
expect(result[:foundation_impacts]).to include(:care, :fairness)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
153
204
|
end
|
|
154
205
|
|
|
155
206
|
describe '.resolve_dilemma' do
|
|
@@ -228,5 +279,58 @@ RSpec.describe Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Llm
|
|
|
228
279
|
expect(result).to be_nil
|
|
229
280
|
end
|
|
230
281
|
end
|
|
282
|
+
|
|
283
|
+
context 'when LLM returns a native content response (direct dispatch path)' do
|
|
284
|
+
let(:native_response) do
|
|
285
|
+
content = <<~TEXT
|
|
286
|
+
CHOSEN: opt_b
|
|
287
|
+
CONFIDENCE: 0.75
|
|
288
|
+
REASONING: Native dispatch selected option b based on deontological constraints.
|
|
289
|
+
TEXT
|
|
290
|
+
double('native_response', content: content)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
before do
|
|
294
|
+
allow(Legion::LLM).to receive(:chat).and_return(native_response)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it 'calls Legion::LLM.chat with system and user messages' do
|
|
298
|
+
expect(Legion::LLM).to receive(:chat).with(
|
|
299
|
+
hash_including(
|
|
300
|
+
message: array_including(
|
|
301
|
+
hash_including(role: 'system'),
|
|
302
|
+
hash_including(role: 'user')
|
|
303
|
+
)
|
|
304
|
+
)
|
|
305
|
+
).and_return(native_response)
|
|
306
|
+
enhancer.resolve_dilemma(
|
|
307
|
+
dilemma_description: 'Should the agent reveal sensitive information?',
|
|
308
|
+
options: options,
|
|
309
|
+
framework: :deontological
|
|
310
|
+
)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it 'calls Legion::LLM.chat with caller metadata' do
|
|
314
|
+
expect(Legion::LLM).to receive(:chat).with(
|
|
315
|
+
hash_including(caller: { extension: 'lex-agentic-social', mode: :moral_reasoning })
|
|
316
|
+
).and_return(native_response)
|
|
317
|
+
enhancer.resolve_dilemma(
|
|
318
|
+
dilemma_description: 'Should the agent reveal sensitive information?',
|
|
319
|
+
options: options,
|
|
320
|
+
framework: :deontological
|
|
321
|
+
)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
it 'uses the content directly without calling ask' do
|
|
325
|
+
expect(native_response).not_to receive(:ask)
|
|
326
|
+
result = enhancer.resolve_dilemma(
|
|
327
|
+
dilemma_description: 'Should the agent reveal sensitive information?',
|
|
328
|
+
options: options,
|
|
329
|
+
framework: :deontological
|
|
330
|
+
)
|
|
331
|
+
expect(result[:chosen_option]).to eq('opt_b')
|
|
332
|
+
expect(result[:confidence]).to eq(0.75)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
231
335
|
end
|
|
232
336
|
end
|