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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e415a1e8df317c84447b21607aa89a50091a52ee603c103532434e613b5c79f8
4
- data.tar.gz: '0485cc912a12c05e87ed0d6a89f34a20e678f92bdb1fd33f40bee1f03fd5f6ce'
3
+ metadata.gz: 2fc82cf70d0df9c909097bbed4d53fc58e64e2ca8904a49c6d7abf030fc3b2c6
4
+ data.tar.gz: 41e0f3966d50d002ccd5cf3a96d488b7bb1d2208cc4f36b931a5fd311786c80a
5
5
  SHA512:
6
- metadata.gz: 73b527381fc34d087abce74fd3fa0f704bd8c2717c5100b2b822a83c8a392c9a79aa5a44e78d9667d6429b733390d31056c1573661e03696ea317fdb25fecb30
7
- data.tar.gz: 37e46998cc523737472ca6884db0a24320c18e252190cef94fefed44e70b70f5c60c57fa30f20832d7aed9cc847fb6d90615d1a7589b754e12099cadde1edbaf
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, confidence: confidence)
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
- chat = Legion::LLM.chat
61
- chat.with_instructions(SYSTEM_PROMPT)
62
- chat.ask(prompt)
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
- chat = Legion::LLM.chat
54
- chat.with_instructions(SYSTEM_PROMPT)
55
- chat.ask(prompt)
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
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Social
7
- VERSION = '0.1.16'
7
+ VERSION = '0.1.17'
8
8
  end
9
9
  end
10
10
  end
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-social
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.16
4
+ version: 0.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity