lex-llm-mlx 0.5.2 → 0.5.3

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: 7e5f9a7dab605748337a587418be077e253a7e6b497c6b014ecba9875043c6ad
4
- data.tar.gz: 3114258fbcc3dd3b6f184f28d83bc14c866187947a08c42bf6128f86e002c9c3
3
+ metadata.gz: 43228a67440228c5db6f468ca4eb81c3fb032477fce92fbc60432c02b0872290
4
+ data.tar.gz: a91c59aa2bfc248de6f3715d41d46cfd2c0a82531df8c6ceceec8f9b73e5c1e9
5
5
  SHA512:
6
- metadata.gz: 12ae5ec7295decb7048148673f35daac26c2737f0739b4d236a2097ac07d6b7c5eddcad7af5bbde8fc7e6c2ca3d4bea570a2021ff6d0b30f2eff1e45711dc836
7
- data.tar.gz: d227264342de019670a47624dcf121ae21c9ed9653d3e65c47d15ff754c05da407b44b29e20de06ee357f21b5bf34fffe7fac9869c1cacecefcd2ed21fd42c62
6
+ metadata.gz: 81faf223f32cd773041faad5637d42e76174ee042e723d50867e8d7b4ba5be8059a6489934118c391efc9e34fa1f5a7d7223cc6c80a9dee85a951cbc0042bf85
7
+ data.tar.gz: 7f19db1af208ead30d11984e3a86c67ac4748f64239802cd7851586990311eee667e35c8394722800f0d26d3bd5db1364617667a3e51ea1fab0d06d68bcb8939
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.3] - 2026-08-19
4
+
5
+ ### Changed
6
+ - Publish the immutable four-component lane-weight pair from MLX discovery and reconcile weight-only changes on the existing scheduled writer pass.
7
+ - Serialize initial, recovery, replacement, removal, and shutdown state transitions behind one actor-local mutex without adding a Settings callback or operator workflow.
8
+ - Track configured-but-unpublished weight keys on the existing discovery cadence and log each dormant transition once.
9
+ - Raise the `lex-llm` dependency floor to 0.7.6; the existing `legion-settings` dependency remains unchanged.
10
+
11
+ ### Fixed
12
+ - Compare every offering contract field while excluding only non-authoritative evidence observation timestamps, preventing unchanged discovery passes from republishing snapshots while retaining real evidence and weight changes.
13
+ - Treat discovery catalogs as order-independent multisets, so provider reorderings do not republish while adding or removing a duplicate offering remains a significant change.
14
+ - Validate the complete weighted offering set before constructing or claiming a callable scope, so malformed weights leave no orphaned Registry publication and a later corrected ordinary pass activates without restart or operator cleanup.
15
+
16
+ ### Added
17
+ - Cover the complete writer lifecycle, publication races, failure atomicity, dormant-state cycle, and the actual callable's folded-system OpenAI-compatible wire payload.
18
+
3
19
  ## [0.5.2] - 2026-08-18
4
20
 
5
21
  ### Fixed
data/lex-llm-mlx.gemspec CHANGED
@@ -28,8 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.add_dependency 'legion-logging', '>= 1.3.2'
29
29
  spec.add_dependency 'legion-settings', '>= 1.4.2'
30
30
  spec.add_dependency 'legion-transport', '>= 1.4.14'
31
- # 0.7.1 carries the SSOT v3 InstanceKey (instance_id = config name +
32
- # secondary physical_id) and the Publisher physical_id: kwargs the
33
- # discovery actor calls. 0.7.0 publishers reject the physical_id: kwarg.
34
- spec.add_dependency 'lex-llm', '>= 0.7.1'
31
+ # 0.7.6 carries the write-time weight schema/reconciler and the immutable
32
+ # OfferingDraft/OfferingRecord/LaneRecord weight pair used by this writer.
33
+ spec.add_dependency 'lex-llm', '>= 0.7.6'
35
34
  end
@@ -21,6 +21,7 @@ require 'legion/extensions/llm/inventory/identity'
21
21
  require 'legion/extensions/llm/inventory/records'
22
22
  require 'legion/extensions/llm/inventory/evidence'
23
23
  require 'legion/extensions/llm/inventory/probe_coordinator'
24
+ require 'legion/extensions/llm/inventory/weight_reconciler'
24
25
  require 'legion/extensions/llm/routing/provider_outcome'
25
26
  require 'legion/extensions/llm/taxonomies'
26
27
  require 'legion/extensions/llm/capabilities'
@@ -46,6 +47,13 @@ module Legion
46
47
  def build_offering_draft(model_id:, model_data:, instance_cfg:, instance_key:)
47
48
  tier = instance_cfg[:tier] || :local
48
49
  embed_supported = embedding_model?(model_id: model_id)
50
+ weight_inputs = Legion::Extensions::Llm::Inventory::WeightSchema.weight_inputs(
51
+ settings: Legion::Settings,
52
+ instance_key: instance_key,
53
+ provider_native_key: model_id,
54
+ model: model_id,
55
+ tier: tier
56
+ )
49
57
 
50
58
  Legion::Extensions::Llm::Inventory::OfferingDraft.new(
51
59
  provider_native_key: model_id,
@@ -62,7 +70,9 @@ module Legion
62
70
  tokenizer_evidence: absent_value_evidence,
63
71
  quota_domains: {},
64
72
  metadata: build_offering_metadata(model_data: model_data, instance_key: instance_key),
65
- publication_source: :provider_catalog
73
+ publication_source: :provider_catalog,
74
+ weight_inputs: weight_inputs,
75
+ base_weight: Legion::Extensions::Llm::Inventory::WeightSchema.base_weight(weight_inputs)
66
76
  )
67
77
  end
68
78
 
@@ -239,8 +249,7 @@ module Legion
239
249
 
240
250
  def readiness_failure(reason:, error:)
241
251
  Legion::Extensions::Llm::Inventory::ReadinessResult.new(
242
- ready: false, reason: reason,
243
- metadata: { error_class: error.class.name }
252
+ ready: false, reason:, metadata: { error_class: error.class.name }
244
253
  )
245
254
  end
246
255
 
@@ -254,9 +263,9 @@ module Legion
254
263
  )
255
264
  readiness = check_health(instance_cfg: state[:instance_cfg])
256
265
  coordinator.finish_probe
257
- report_probe_result(
266
+ commit_probe_result(
258
267
  instance_id: instance_id, physical_id: state[:physical_id],
259
- probe_token: probe_token, readiness: readiness
268
+ probe_token: probe_token, readiness: readiness, state: state
260
269
  )
261
270
  rescue StandardError => e
262
271
  begin
@@ -268,21 +277,15 @@ module Legion
268
277
  end
269
278
 
270
279
  def handle_reactive_probe(instance_id:, request:)
271
- state = @instance_states[instance_id]
280
+ state = state_mutex.synchronize { @instance_states[instance_id] }
272
281
  return unless state
273
282
 
274
283
  coordinator = state[:probe_coordinator]
275
284
  return unless coordinator.begin_probe(request: request)
276
285
 
277
- probe_token = publisher.readiness_probe_started(
278
- instance_id: instance_id, physical_id: state[:physical_id],
279
- publisher_token: state[:publisher_token]
280
- )
281
- readiness = check_health(instance_cfg: state[:instance_cfg])
282
- coordinator.finish_probe(request: request)
283
- report_probe_result(
284
- instance_id: instance_id, physical_id: state[:physical_id],
285
- probe_token: probe_token, readiness: readiness
286
+ perform_reactive_probe(
287
+ instance_id: instance_id, request: request,
288
+ state: state, coordinator: coordinator
286
289
  )
287
290
  rescue StandardError => e
288
291
  begin
@@ -293,15 +296,33 @@ module Legion
293
296
  handle_exception(e, level: :warn, operation: 'mlx.actor.reactive_probe', instance_id: instance_id)
294
297
  end
295
298
 
296
- def report_probe_result(instance_id:, physical_id:, probe_token:, readiness:)
297
- if readiness.ready?
298
- publisher.readiness_succeeded(
299
- instance_id: instance_id, physical_id: physical_id, probe_token: probe_token
300
- )
301
- else
302
- publisher.readiness_failed(
303
- instance_id: instance_id, physical_id: physical_id, probe_token: probe_token, reason: readiness.reason
304
- )
299
+ def perform_reactive_probe(instance_id:, request:, state:, coordinator:)
300
+ probe_token = publisher.readiness_probe_started(
301
+ instance_id: instance_id, physical_id: state[:physical_id],
302
+ publisher_token: state[:publisher_token]
303
+ )
304
+ readiness = check_health(instance_cfg: state[:instance_cfg])
305
+ coordinator.finish_probe(request: request)
306
+ commit_probe_result(
307
+ instance_id: instance_id, physical_id: state[:physical_id],
308
+ probe_token: probe_token, readiness: readiness, state: state
309
+ )
310
+ end
311
+
312
+ def commit_probe_result(instance_id:, physical_id:, probe_token:, readiness:, state:)
313
+ state_mutex.synchronize do
314
+ return unless @instance_states[instance_id].equal?(state)
315
+
316
+ if readiness.ready?
317
+ publisher.readiness_succeeded(
318
+ instance_id: instance_id, physical_id: physical_id, probe_token: probe_token
319
+ )
320
+ else
321
+ publisher.readiness_failed(
322
+ instance_id: instance_id, physical_id: physical_id,
323
+ probe_token: probe_token, reason: readiness.reason
324
+ )
325
+ end
305
326
  end
306
327
  end
307
328
 
@@ -316,6 +337,135 @@ module Legion
316
337
  end
317
338
  end
318
339
 
340
+ # MLX offering comparison excludes only evidence observation telemetry.
341
+ module OfferingComparison
342
+ OFFERING_SCALAR_EVIDENCE_FIELDS = %i[
343
+ context_evidence max_output_evidence embedding_dimensions_evidence
344
+ model_revision_evidence tokenizer_evidence
345
+ ].freeze
346
+
347
+ private
348
+
349
+ # Inventory evidence timestamps are telemetry only: Evidence explicitly
350
+ # excludes observed_at from authority, ordering, freshness, recovery, and
351
+ # selection. Compare every OfferingDraft field (including the stored weight
352
+ # pair) while removing only those volatile evidence timestamps.
353
+ def offerings_equivalent?(previous, current)
354
+ Array(previous).map { |draft| offering_comparison_state(draft) }.tally ==
355
+ Array(current).map { |draft| offering_comparison_state(draft) }.tally
356
+ end
357
+
358
+ def offering_comparison_state(draft)
359
+ state = draft.to_h
360
+ state[:operation_evidence] = comparison_evidence_map(draft.operation_evidence)
361
+ state[:capability_evidence] = comparison_evidence_map(draft.capability_evidence)
362
+ OFFERING_SCALAR_EVIDENCE_FIELDS.each do |field|
363
+ state[field] = comparison_evidence(draft.public_send(field))
364
+ end
365
+ state
366
+ end
367
+
368
+ def comparison_evidence_map(evidence)
369
+ evidence.transform_values { |entry| comparison_evidence(entry) }
370
+ end
371
+
372
+ def comparison_evidence(evidence)
373
+ evidence.to_h.except(:observed_at)
374
+ end
375
+ end
376
+
377
+ # MLX bindings for the shared writer reconciler. This module owns only
378
+ # actor-local publication synchronization and dormant tracking used by
379
+ # the existing discovery cadence.
380
+ module WeightPublication
381
+ private
382
+
383
+ def replace_offerings_if_changed(instance_id:, state:)
384
+ new_offerings = discover_offerings_for_instance(
385
+ instance_cfg: state[:instance_cfg], instance_key: state[:instance_key]
386
+ )
387
+ Legion::Extensions::Llm::Inventory::WeightReconciler.commit_if_changed!(
388
+ settings: Legion::Settings,
389
+ instance_id: instance_id,
390
+ state: state,
391
+ discovered_offerings: new_offerings,
392
+ mutex: state_mutex,
393
+ equivalent: method(:offerings_equivalent?),
394
+ replace: method(:replace_weight_snapshot)
395
+ )
396
+ end
397
+
398
+ def replace_weight_snapshot(instance_id:, state:, offerings:, sequence:)
399
+ publisher.replace_instance_snapshot(
400
+ instance_id: instance_id,
401
+ publisher_token: state.fetch(:publisher_token),
402
+ offerings: offerings,
403
+ sequence: sequence,
404
+ physical_id: state.fetch(:physical_id)
405
+ )
406
+ end
407
+
408
+ def commit_readiness(instance_id:, probe_token:, readiness:, state:)
409
+ if readiness.ready?
410
+ return Legion::Extensions::Llm::Inventory::WeightReconciler.activate_tracked!(
411
+ settings: Legion::Settings,
412
+ instance_id: instance_id,
413
+ state_key: instance_id,
414
+ state: state,
415
+ states: @instance_states,
416
+ mutex: state_mutex,
417
+ probe_token: probe_token,
418
+ activate: method(:activate_weight_snapshot),
419
+ activation_sequence: ->(tracked) { tracked.fetch(:sequence) }
420
+ )
421
+ end
422
+
423
+ state_mutex.synchronize do
424
+ return false unless @instance_states[instance_id].equal?(state)
425
+
426
+ publisher.readiness_failed(
427
+ instance_id: instance_id, physical_id: state[:physical_id],
428
+ probe_token: probe_token, reason: readiness.reason
429
+ )
430
+ end
431
+ true
432
+ end
433
+
434
+ def activate_weight_snapshot(instance_id:, state:, offerings:, sequence:, probe_token:)
435
+ publisher.activate_instance_snapshot(
436
+ instance_id: instance_id,
437
+ publisher_token: state.fetch(:publisher_token),
438
+ offerings: offerings,
439
+ sequence: sequence,
440
+ probe_token: probe_token,
441
+ physical_id: state.fetch(:physical_id)
442
+ )
443
+ end
444
+
445
+ def observe_dormant_weights
446
+ Legion::Extensions::Llm::Inventory::WeightReconciler.observe_dormant!(
447
+ settings: Legion::Settings,
448
+ provider_family: :mlx,
449
+ states: @instance_states,
450
+ mutex: state_mutex,
451
+ tracker: dormant_weight_tracker,
452
+ dormant_logger: lambda do |key|
453
+ log.info(
454
+ "[llm][mlx] action=dormant_weight weight_key=#{key.inspect} no_lane_published=true"
455
+ )
456
+ end
457
+ )
458
+ end
459
+
460
+ def state_mutex
461
+ @state_mutex ||= Mutex.new
462
+ end
463
+
464
+ def dormant_weight_tracker
465
+ @dormant_weight_tracker ||= Legion::Extensions::Llm::Inventory::DormantWeightTracker.new
466
+ end
467
+ end
468
+
319
469
  # Periodic refresh cycle — included by DiscoveryRefresh. Each tick
320
470
  # re-scans the configured instances (late configuration appears
321
471
  # without a restart; removed instances are reconciled out),
@@ -326,30 +476,40 @@ module Legion
326
476
  private
327
477
 
328
478
  def tick_refresh
329
- @instance_states ||= {}
479
+ instance_states
330
480
  configured_ids = {}
331
481
  configured_instances.each do |name, instance_cfg|
332
- # Identity is the operator's CONFIG NAME — the key the
333
- # frozen config uses and the router keys instances.<name>
334
- # settings lookups by. Two names at the same endpoint stay
335
- # distinct instances (the physical id is secondary).
336
- instance_id = name.to_s
337
- configured_ids[instance_id] = true
338
- state = @instance_states[instance_id]
339
- if state.nil?
340
- claim_and_activate_instance(name: name, instance_cfg: instance_cfg)
341
- else
342
- refresh_instance(instance_id: instance_id, name: name, state: state)
343
- end
344
- rescue StandardError => e
345
- handle_exception(e, level: :warn, operation: 'mlx.actor.tick_refresh', instance_name: name.to_s)
482
+ reconcile_configured_instance(
483
+ name: name, instance_cfg: instance_cfg, configured_ids: configured_ids
484
+ )
346
485
  end
347
486
 
348
487
  remove_unconfigured_instances(configured_ids: configured_ids)
488
+ observe_dormant_weights
489
+ end
490
+
491
+ def instance_states
492
+ state_mutex.synchronize { @instance_states ||= {} }
493
+ end
494
+
495
+ # Identity is the operator's config name — the key the frozen config
496
+ # and router use. Two names at one endpoint remain distinct instances.
497
+ def reconcile_configured_instance(name:, instance_cfg:, configured_ids:)
498
+ instance_id = name.to_s
499
+ configured_ids[instance_id] = true
500
+ state = state_mutex.synchronize { @instance_states[instance_id] }
501
+ if state.nil?
502
+ claim_and_activate_instance(name: name, instance_cfg: instance_cfg)
503
+ else
504
+ refresh_instance(instance_id: instance_id, name: name, state: state)
505
+ end
506
+ rescue StandardError => e
507
+ handle_exception(e, level: :warn, operation: 'mlx.actor.tick_refresh', instance_name: name.to_s)
349
508
  end
350
509
 
351
510
  def remove_unconfigured_instances(configured_ids:)
352
- @instance_states.each do |instance_id, state|
511
+ states = state_mutex.synchronize { @instance_states.to_a }
512
+ states.each do |instance_id, state|
353
513
  next if configured_ids.key?(instance_id)
354
514
 
355
515
  remove_instance_state(instance_id: instance_id, state: state)
@@ -360,12 +520,18 @@ module Legion
360
520
  end
361
521
 
362
522
  def remove_instance_state(instance_id:, state:)
363
- publisher.remove_instance(
364
- instance_id: instance_id, physical_id: state[:physical_id],
365
- publisher_token: state[:publisher_token]
366
- )
367
- clear_instance_health(config_name: state[:name])
368
- @instance_states.delete(instance_id)
523
+ removed = state_mutex.synchronize do
524
+ next false unless @instance_states[instance_id].equal?(state)
525
+
526
+ publisher.remove_instance(
527
+ instance_id: instance_id, physical_id: state[:physical_id],
528
+ publisher_token: state[:publisher_token]
529
+ )
530
+ @instance_states.delete(instance_id)
531
+ true
532
+ end
533
+ clear_instance_health(config_name: state[:name]) if removed
534
+ removed
369
535
  end
370
536
 
371
537
  def refresh_instance(instance_id:, name:, state:)
@@ -380,21 +546,6 @@ module Legion
380
546
  write_instance_health(config_name: name, state: state)
381
547
  end
382
548
 
383
- def replace_offerings_if_changed(instance_id:, state:)
384
- new_offerings = discover_offerings_for_instance(
385
- instance_cfg: state[:instance_cfg], instance_key: state[:instance_key]
386
- )
387
- return if new_offerings == state[:offerings]
388
-
389
- state[:sequence] += 1
390
- publisher.replace_instance_snapshot(
391
- instance_id: instance_id, physical_id: state[:physical_id],
392
- publisher_token: state[:publisher_token],
393
- offerings: new_offerings, sequence: state[:sequence]
394
- )
395
- state[:offerings] = new_offerings
396
- end
397
-
398
549
  # Initial-failure recovery: an instance stuck at :initializing
399
550
  # (readiness failed at boot, e.g. transient outage) re-activates
400
551
  # on the first healthy probe. While :initializing,
@@ -405,34 +556,24 @@ module Legion
405
556
  offerings = discover_offerings_for_instance(
406
557
  instance_cfg: state[:instance_cfg], instance_key: state[:instance_key]
407
558
  )
559
+ Legion::Extensions::Llm::Inventory::WeightReconciler.commit_if_changed!(
560
+ settings: Legion::Settings,
561
+ instance_id: instance_id,
562
+ state: state,
563
+ discovered_offerings: offerings,
564
+ mutex: state_mutex,
565
+ equivalent: method(:offerings_equivalent?),
566
+ replace: method(:replace_weight_snapshot)
567
+ )
408
568
  probe_token = publisher.readiness_probe_started(
409
569
  instance_id: instance_id, publisher_token: state[:publisher_token]
410
570
  )
411
571
  readiness = check_health(instance_cfg: state[:instance_cfg])
412
- commit_readiness(instance_id: instance_id, offerings: offerings,
413
- probe_token: probe_token, readiness: readiness, state: state)
414
- write_instance_health(config_name: name, state: state)
415
- end
416
-
417
- # Shared commit for initial and recovery activation: on a
418
- # healthy probe activate the snapshot (the only legal commit
419
- # from :initializing), otherwise record the failed readiness.
420
- # The sequence is the instance state's sequence (0 until the
421
- # first replace after activation).
422
- def commit_readiness(instance_id:, offerings:, probe_token:, readiness:, state:)
423
- if readiness.ready?
424
- publisher.activate_instance_snapshot(
425
- instance_id: instance_id, physical_id: state[:physical_id],
426
- publisher_token: state[:publisher_token],
427
- offerings: offerings, sequence: state[:sequence], probe_token: probe_token
428
- )
429
- state[:offerings] = offerings
430
- else
431
- publisher.readiness_failed(
432
- instance_id: instance_id, physical_id: state[:physical_id],
433
- probe_token: probe_token, reason: readiness.reason
434
- )
435
- end
572
+ committed = commit_readiness(
573
+ instance_id: instance_id, probe_token: probe_token,
574
+ readiness: readiness, state: state
575
+ )
576
+ write_instance_health(config_name: name, state: state) if committed
436
577
  end
437
578
  end
438
579
 
@@ -489,7 +630,7 @@ module Legion
489
630
  end
490
631
 
491
632
  def build_instance_state(**attrs)
492
- attrs.merge(sequence: 0)
633
+ attrs.merge(sequence: 0, published: false)
493
634
  end
494
635
  end
495
636
 
@@ -622,6 +763,8 @@ module Legion
622
763
  include ValueEvidenceBuilding
623
764
  include OfferingAssembly
624
765
  include HealthProbing
766
+ include OfferingComparison
767
+ include WeightPublication
625
768
  include TickCycle
626
769
  include InstanceConfig
627
770
  include HealthDisplay
@@ -672,6 +815,7 @@ module Legion
672
815
  instance_id = name.to_s
673
816
  physical_id = derive_physical_id(instance_cfg: instance_cfg)
674
817
  instance_key = build_instance_key(instance_id: instance_id, physical_id: physical_id)
818
+ offerings = discover_offerings_for_instance(instance_cfg: instance_cfg, instance_key: instance_key)
675
819
  callable = Legion::Extensions::Llm::Mlx::Actor::MlxCallable.new(instance_cfg: instance_cfg, logger: log)
676
820
  probe_coordinator = build_probe_coordinator(instance_id: instance_id, instance_key: instance_key)
677
821
  publisher_token = publisher.claim_instance(
@@ -680,42 +824,50 @@ module Legion
680
824
  )
681
825
  run_activation(
682
826
  instance_id: instance_id, publisher_token: publisher_token,
827
+ offerings: offerings,
683
828
  instance_desc: { name: name, instance_id: instance_id, physical_id: physical_id,
684
829
  instance_key: instance_key, instance_cfg: instance_cfg,
685
830
  callable: callable, probe_coordinator: probe_coordinator }
686
831
  )
687
832
  end
688
833
 
689
- def run_activation(instance_id:, publisher_token:, instance_desc:)
834
+ def run_activation(instance_id:, publisher_token:, offerings:, instance_desc:)
690
835
  instance_cfg = instance_desc[:instance_cfg]
691
- instance_key = instance_desc[:instance_key]
692
- offerings = discover_offerings_for_instance(instance_cfg: instance_cfg, instance_key: instance_key)
836
+ state = build_instance_state(
837
+ **instance_desc, publisher_token: publisher_token, offerings: offerings
838
+ )
839
+ Legion::Extensions::Llm::Inventory::WeightReconciler.track_initializing!(
840
+ states: @instance_states,
841
+ state_key: instance_id,
842
+ state: state,
843
+ mutex: state_mutex
844
+ )
693
845
  probe_token = publisher.readiness_probe_started(instance_id: instance_id,
694
846
  publisher_token: publisher_token)
695
847
  readiness = check_health(instance_cfg: instance_cfg)
696
- state = build_instance_state(
697
- **instance_desc, publisher_token: publisher_token, offerings: offerings
848
+ committed = commit_readiness(
849
+ instance_id: instance_id, probe_token: probe_token,
850
+ readiness: readiness, state: state
698
851
  )
699
- commit_readiness(instance_id: instance_id, offerings: offerings,
700
- probe_token: probe_token, readiness: readiness, state: state)
701
- @instance_states[instance_id] = state
702
- write_instance_health(config_name: instance_desc[:name], state: state)
852
+ write_instance_health(config_name: instance_desc[:name], state: state) if committed
703
853
  end
704
854
 
705
855
  def remove_all_instances
706
- return unless @instance_states
856
+ states = state_mutex.synchronize do
857
+ return if @instance_states.nil?
707
858
 
708
- @instance_states.each do |instance_id, state|
709
- publisher.remove_instance(
710
- instance_id: instance_id, physical_id: state[:physical_id],
711
- publisher_token: state[:publisher_token]
712
- )
713
- clear_instance_health(config_name: state[:name])
859
+ @instance_states.to_a
860
+ end
861
+ states.each do |instance_id, state|
862
+ remove_instance_state(instance_id: instance_id, state: state)
714
863
  rescue StandardError => e
715
864
  handle_exception(e, level: :warn, operation: 'mlx.actor.remove_instance',
716
865
  instance_id: instance_id)
717
866
  end
718
- @instance_states.clear
867
+ state_mutex.synchronize do
868
+ @instance_states.clear
869
+ dormant_weight_tracker.clear!
870
+ end
719
871
  end
720
872
  end
721
873
 
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Mlx
7
- VERSION = '0.5.2'
7
+ VERSION = '0.5.3'
8
8
  end
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-mlx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -85,14 +85,14 @@ dependencies:
85
85
  requirements:
86
86
  - - ">="
87
87
  - !ruby/object:Gem::Version
88
- version: 0.7.1
88
+ version: 0.7.6
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - ">="
94
94
  - !ruby/object:Gem::Version
95
- version: 0.7.1
95
+ version: 0.7.6
96
96
  description: MLX provider integration for the LegionIO LLM routing framework.
97
97
  email:
98
98
  - matthewdiverson@gmail.com