hugpy-engine 0.2.3__tar.gz → 0.2.5__tar.gz

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.
Files changed (207) hide show
  1. {hugpy_engine-0.2.3/src/hugpy_engine.egg-info → hugpy_engine-0.2.5}/PKG-INFO +1 -1
  2. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/get_module.py +50 -2
  3. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/catalog_bridge.py +8 -1
  4. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/main.py +16 -0
  5. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/models/model_meta.py +10 -2
  6. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/models/models_config.py +176 -0
  7. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/dispatch/activity.py +14 -2
  8. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/dispatch/dispatch.py +20 -3
  9. hugpy_engine-0.2.5/src/hugpy_engine/fit/__init__.py +28 -0
  10. hugpy_engine-0.2.5/src/hugpy_engine/fit/flex.py +689 -0
  11. hugpy_engine-0.2.5/src/hugpy_engine/fit/plan.py +587 -0
  12. hugpy_engine-0.2.5/src/hugpy_engine/fit/types.py +428 -0
  13. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/generate/config.py +77 -6
  14. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/get.py +41 -9
  15. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/base_runner.py +53 -2
  16. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/ccp_runner.py +15 -0
  17. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/python_runner.py +35 -0
  18. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/peft_adapters.py +9 -2
  19. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/__init__.py +2 -1
  20. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/assure_model_key.py +195 -7
  21. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/categories/builders.py +5 -1
  22. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/model_resolver.py +17 -2
  23. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/remote.py +778 -45
  24. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/chat_schemas.py +18 -0
  25. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/event_schemas.py +10 -0
  26. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/metadata_schemas.py +1 -0
  27. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/load_failure.py +84 -1
  28. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/overrides.py +59 -75
  29. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/slot_agent.py +27 -0
  30. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/slots.py +169 -114
  31. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/spill.py +62 -16
  32. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/chat_schemas.py +7 -0
  33. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5/src/hugpy_engine.egg-info}/PKG-INFO +1 -1
  34. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/SOURCES.txt +22 -1
  35. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/scm_file_list.json +21 -0
  36. hugpy_engine-0.2.5/src/hugpy_engine.egg-info/scm_version.json +8 -0
  37. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/conftest.py +18 -0
  38. hugpy_engine-0.2.5/tests/known_good/test_call_queue_contract.py +357 -0
  39. hugpy_engine-0.2.5/tests/known_good/test_dependency_acquisition_contract.py +252 -0
  40. hugpy_engine-0.2.5/tests/known_good/test_evict_fit_contract.py +198 -0
  41. hugpy_engine-0.2.5/tests/known_good/test_key_equivalence_contract.py +99 -0
  42. hugpy_engine-0.2.5/tests/known_good/test_missing_dependency_contract.py +116 -0
  43. hugpy_engine-0.2.5/tests/known_good/test_model_format_contract.py +193 -0
  44. hugpy_engine-0.2.5/tests/known_good/test_model_key_contract.py +318 -0
  45. hugpy_engine-0.2.5/tests/known_good/test_plan_fit_contract.py +762 -0
  46. hugpy_engine-0.2.5/tests/known_good/test_politeness_retired_contract.py +65 -0
  47. hugpy_engine-0.2.5/tests/known_good/test_prefill_progress_contract.py +99 -0
  48. hugpy_engine-0.2.5/tests/known_good/test_relay_gate_allocation_contract.py +111 -0
  49. hugpy_engine-0.2.5/tests/known_good/test_relay_stream_stall_contract.py +279 -0
  50. hugpy_engine-0.2.5/tests/known_good/test_remote_error_structure_contract.py +133 -0
  51. hugpy_engine-0.2.5/tests/known_good/test_response_format_contract.py +104 -0
  52. hugpy_engine-0.2.5/tests/known_good/test_slot_seat_admission_contract.py +196 -0
  53. hugpy_engine-0.2.5/tests/known_good/test_slot_serialization_contract.py +159 -0
  54. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_central_cap_gating.py +38 -7
  55. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_chat_role_alternation.py +36 -8
  56. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_ctx_fit.py +8 -0
  57. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_hot_cache.py +27 -0
  58. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_per_request_alloc.py +5 -0
  59. hugpy_engine-0.2.5/tests/test_slot_agent_loading_state.py +75 -0
  60. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_spill_ctx_reserve.py +20 -12
  61. hugpy_engine-0.2.3/src/hugpy_engine.egg-info/scm_version.json +0 -8
  62. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/LICENSE +0 -0
  63. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/README.md +0 -0
  64. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/docs/ALLOCATION-MODES.md +0 -0
  65. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/pyproject.toml +0 -0
  66. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/setup.cfg +0 -0
  67. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/__init__.py +0 -0
  68. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/alloc_modes.py +0 -0
  69. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/allocation.py +0 -0
  70. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/__init__.py +0 -0
  71. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/call_api.py +0 -0
  72. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/reclassify.py +0 -0
  73. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/reconcile.py +0 -0
  74. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/serve/__init__.py +0 -0
  75. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/serve/serve.py +0 -0
  76. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/serve/serve_cli.py +0 -0
  77. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/apis/systemd_units.py +0 -0
  78. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/backend.py +0 -0
  79. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/backends/__init__.py +0 -0
  80. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/backends/local.py +0 -0
  81. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/catalog.py +0 -0
  82. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/categories.py +0 -0
  83. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/chat_context/__init__.py +0 -0
  84. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/chat_context/chat_context.py +0 -0
  85. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/chat_context/context_budget.py +0 -0
  86. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/chat_context/unbounded.py +0 -0
  87. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/__init__.py +0 -0
  88. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/models/__init__.py +0 -0
  89. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/models/models_default.py +0 -0
  90. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/config/models/models_dict.py +0 -0
  91. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/contracts.py +0 -0
  92. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/dispatch/__init__.py +0 -0
  93. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/dispatch/acquire.py +0 -0
  94. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/draft_models.py +0 -0
  95. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/engines.py +0 -0
  96. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/eviction.py +0 -0
  97. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/generate/__init__.py +0 -0
  98. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/generate/coder.py +0 -0
  99. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/generate/generate_runner.py +0 -0
  100. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/gguf_election.py +0 -0
  101. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/__init__.py +0 -0
  102. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/__init__.py +0 -0
  103. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/chat_runner.py +0 -0
  104. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/__init__.py +0 -0
  105. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/imports/__init__.py +0 -0
  106. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/imports/config.py +0 -0
  107. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/imports/constants.py +0 -0
  108. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/imports/init_imports.py +0 -0
  109. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/imports/utils.py +0 -0
  110. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/runners/src/shard_server.py +0 -0
  111. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/llama/serve.py +0 -0
  112. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/manifest.py +0 -0
  113. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_classifier.py +0 -0
  114. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_index/__init__.py +0 -0
  115. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_index/client.py +0 -0
  116. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_index/query_registry.py +0 -0
  117. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_index/repositories.py +0 -0
  118. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/model_index/service.py +0 -0
  119. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/name_match.py +0 -0
  120. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/native/__init__.py +0 -0
  121. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/native/build.py +0 -0
  122. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/native/fetch.py +0 -0
  123. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/native/resolve.py +0 -0
  124. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/output_repair.py +0 -0
  125. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/placement.py +0 -0
  126. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/py.typed +0 -0
  127. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/query.py +0 -0
  128. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/allocator.py +0 -0
  129. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/categories/__init__.py +0 -0
  130. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/categories/frameworks.py +0 -0
  131. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/device_placement.py +0 -0
  132. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/groups.py +0 -0
  133. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/resolvers/model_dict_resolver.py +0 -0
  134. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/routing_diagnostics.py +0 -0
  135. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/runtime.py +0 -0
  136. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/__init__.py +0 -0
  137. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/model_schemas.py +0 -0
  138. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/runner_schemas.py +0 -0
  139. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/schemas/task_schemas.py +0 -0
  140. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/__init__.py +0 -0
  141. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/chunksum_verify.py +0 -0
  142. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/hot_cache.py +0 -0
  143. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/model_cache.py +0 -0
  144. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/policy.py +0 -0
  145. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/profiles.py +0 -0
  146. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/serve.py +0 -0
  147. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/serve_cli.py +0 -0
  148. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/serve/supervisor.py +0 -0
  149. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/task_deps.py +0 -0
  150. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/tasks.py +0 -0
  151. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/utils/__init__.py +0 -0
  152. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/utils/json_scavenge.py +0 -0
  153. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/utils/no_think.py +0 -0
  154. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/__init__.py +0 -0
  155. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/config_schemas.py +0 -0
  156. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/install_schemas.py +0 -0
  157. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/model_schemas.py +0 -0
  158. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/request_schemas.py +0 -0
  159. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine/wire/specs_schemas.py +0 -0
  160. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/dependency_links.txt +0 -0
  161. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/entry_points.txt +0 -0
  162. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/requires.txt +0 -0
  163. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/src/hugpy_engine.egg-info/top_level.txt +0 -0
  164. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_admission_gate_and_diagnostics.py +0 -0
  165. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_allocation_accuracy.py +0 -0
  166. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_budget_bar_spec.py +0 -0
  167. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_catalog_bridge.py +0 -0
  168. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_chat_request_caller.py +0 -0
  169. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_chat_tool_messages_schema.py +0 -0
  170. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_chunksum_verify.py +0 -0
  171. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_comms_foundations.py +0 -0
  172. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_device_placement.py +0 -0
  173. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_engine_boundary.py +0 -0
  174. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_engine_persisted_resolve.py +0 -0
  175. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_eviction_flow.py +0 -0
  176. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_feasible_distribution_engine.py +0 -0
  177. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_gguf_election.py +0 -0
  178. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_gguf_sharded_effective.py +0 -0
  179. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_image_task_classification.py +0 -0
  180. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_import_policy.py +0 -0
  181. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_load_failure_reason.py +0 -0
  182. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_mmproj_effective_bytes.py +0 -0
  183. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_model_aliases.py +0 -0
  184. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_no_makeroom.py +0 -0
  185. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_no_think.py +0 -0
  186. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_peft_adapter_load.py +0 -0
  187. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_per_device_slot.py +0 -0
  188. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_placement_defaults.py +0 -0
  189. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_provision_failure_honesty.py +0 -0
  190. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_registry_attribution.py +0 -0
  191. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_registry_symmetry.py +0 -0
  192. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_resolve_routing_key_reconcile.py +0 -0
  193. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_runner_registry_heartbeat_starvation.py +0 -0
  194. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_slot_load_stall_health.py +0 -0
  195. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_slot_moe_vram_preflight.py +0 -0
  196. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_slot_placement_projection.py +0 -0
  197. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_slot_port_guard.py +0 -0
  198. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_slot_relaunch.py +0 -0
  199. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_spill_vision_fit.py +0 -0
  200. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_tasks_registry.py +0 -0
  201. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_tok_s_plausible_ceiling.py +0 -0
  202. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_transformers_placement.py +0 -0
  203. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_video_model_classification.py +0 -0
  204. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_vision_needs_slot.py +0 -0
  205. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_vram_cliff.py +0 -0
  206. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_worker_error_attribution.py +0 -0
  207. {hugpy_engine-0.2.3 → hugpy_engine-0.2.5}/tests/test_worker_serve_path_central_only.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hugpy-engine
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: Hugpy engine: model discovery and classification, allocation and eviction, native llama.cpp and GGUF runners, and the prompt-to-reply inference path with task and placement seams
5
5
  Author-email: putkoff <support@hugpy.ai>
6
6
  License-Expression: LicenseRef-Proprietary
@@ -133,6 +133,25 @@ def get_all_configs(verbose: bool = False, get_code: bool = False,
133
133
 
134
134
  hub_id = clean_hub_id(directory, folder)
135
135
  framework = (marker or {}).get("framework") or infer_framework(directory)
136
+ marked_context = (marker or {}).get("model_max_length")
137
+ if (isinstance(marked_context, (int, float)) and
138
+ not isinstance(marked_context, bool) and marked_context > 0):
139
+ max_model_length = int(marked_context)
140
+ elif framework == "gguf":
141
+ # Legacy registry walk also needs the trained context. The generic
142
+ # JSON scanner has no GGUF metadata and otherwise substitutes the
143
+ # global default for models whose marker predates context capture.
144
+ try:
145
+ from hugpy_engine.config.main import get_gguf_file
146
+ from hugpy_storage.gguf_inspect import gguf_metadata
147
+ gguf_path = get_gguf_file(
148
+ directory, None, prefer=(marker or {}).get("filename"))
149
+ context = (gguf_metadata(gguf_path, (".context_length",))
150
+ .get(".context_length") if gguf_path else None)
151
+ if isinstance(context, (int, float)) and not isinstance(context, bool) and context > 0:
152
+ max_model_length = int(context)
153
+ except Exception as exc: # noqa: BLE001 — best-effort metadata only
154
+ logger.debug("GGUF context unavailable for %s: %s", name, exc)
136
155
  tasks = (marker or {}).get("tasks") or tasks_data.get(name)
137
156
  primary_task = (marker or {}).get("primary_task") or (tasks[0] if tasks else None)
138
157
 
@@ -278,12 +297,36 @@ def resolve_hugpy_marker(directory: str, hub_id: str) -> dict:
278
297
  tasks = marker.get("tasks")
279
298
  if tasks is not None and not isinstance(tasks, list):
280
299
  tasks = [tasks]
281
- return {k: v for k, v in {
300
+ out = {k: v for k, v in {
282
301
  "name": marker.get("name"),
283
302
  "framework": marker.get("framework"),
284
303
  "tasks": tasks,
285
304
  "primary_task": marker.get("primary_task"),
286
305
  }.items() if v is not None}
306
+ context = marker.get("model_max_length")
307
+ if isinstance(context, (int, float)) and not isinstance(context, bool) and context > 0:
308
+ out["model_max_length"] = int(context)
309
+ return out
310
+
311
+
312
+ def resolve_gguf_context(directory: str, hub_id: str) -> dict:
313
+ """Read the selected GGUF's trained context from its header."""
314
+ try:
315
+ from hugpy_storage.hugpy_marker import read_hugpy_marker
316
+ marker = read_hugpy_marker(directory) or {}
317
+ if (marker.get("framework") or infer_framework(directory)) != "gguf":
318
+ return {}
319
+ from hugpy_engine.config.main import get_gguf_file
320
+ from hugpy_storage.gguf_inspect import gguf_metadata
321
+ gguf = get_gguf_file(directory, None, prefer=marker.get("filename"))
322
+ if not gguf:
323
+ return {}
324
+ value = gguf_metadata(gguf, (".context_length",)).get(".context_length")
325
+ if isinstance(value, (int, float)) and not isinstance(value, bool) and value > 0:
326
+ return {"model_max_length": int(value)}
327
+ except Exception as exc: # noqa: BLE001 — metadata inspection must not block discovery
328
+ logger.debug("GGUF context unavailable for %s: %s", hub_id, exc)
329
+ return {}
287
330
 
288
331
 
289
332
  # Storage families that imply a framework. "misc" implies nothing — comfy and
@@ -374,6 +417,7 @@ def build_resolver_chain(*, api: Any = None,
374
417
  chain: List[Tuple[str, ResolverFn]] = [
375
418
  ("dir_declaration", resolve_dir_declaration),
376
419
  ("hugpy_marker", resolve_hugpy_marker),
420
+ ("gguf_context", resolve_gguf_context),
377
421
  ("local_config", resolve_local_config),
378
422
  ("local_tokenizer", resolve_local_tokenizer),
379
423
  ]
@@ -441,7 +485,8 @@ def discover_model(save_json: bool = True, verbose: bool = True, use_hub: bool =
441
485
  "architectures": meta.architectures,
442
486
  "model_type": meta.model_type,
443
487
  "max_position_embeddings": meta.max_position_embeddings,
444
- "model_max_length": meta.tokenizer_model_max_length
488
+ "model_max_length": meta.model_max_length
489
+ or meta.tokenizer_model_max_length
445
490
  or meta.max_position_embeddings
446
491
  or DEFAULT_MAX_TOKENS,
447
492
  "parameter_count": meta.parameter_count,
@@ -484,6 +529,9 @@ def discover_models(save_json: bool = True, verbose: bool = True, use_hub: bool
484
529
  meta, meta_sources = enrich(directory, hub_id, chain)
485
530
 
486
531
  row = meta.to_dict()
532
+ row["model_max_length"] = (meta.model_max_length
533
+ or meta.tokenizer_model_max_length
534
+ or meta.max_position_embeddings)
487
535
  # Declared name (hugpy.json) wins; the bare basename is the fallback —
488
536
  # a marker-stamped "comfy-sd-turbo" must not degrade to "sd-turbo" and
489
537
  # collide with the staple's display name.
@@ -52,7 +52,14 @@ class EngineCatalogSource:
52
52
  def canonical_key(self, model_key: str) -> Optional[str]:
53
53
  from hugpy_engine.resolvers.assure_model_key import assure_model_key
54
54
 
55
- return assure_model_key(model_key)
55
+ # IDENTITY resolution only (operator rule 2026-09-29): this is the
56
+ # storage/provision seam a WORKER uses to re-resolve a key central
57
+ # already resolved (ensure_model_registered -> _assure_local_key). The
58
+ # shorthand fuzzy tier must never answer here — on ae it turned
59
+ # ``Qwen3.8-9B-GGUF`` into the resident ``Qwen3.8-9B-Distill-GGUF``
60
+ # (same file, different model) and served it (S3/F7). None means
61
+ # "learn the row from central", never "use the nearest sibling".
62
+ return assure_model_key(model_key, fuzzy=False)
56
63
 
57
64
  def resolve_dir(self, model_key: str) -> Optional[str]:
58
65
  from hugpy_engine.config.main import get_model_path
@@ -50,6 +50,14 @@ def _resolve_model_key(model_key, registry, prefer=None):
50
50
  def _bare(k): # name without the "owner~" qualifier
51
51
  return k.split("~", 1)[1] if "~" in k else k
52
52
  candidates = sorted(k for k in registry if _bare(k) == model_key)
53
+ if not candidates:
54
+ # FORMAT EQUIVALENCE (operator rule 2026-09-29): ``X`` and ``X-GGUF``
55
+ # are one key, so either spelling resolves to the row on record under
56
+ # the other. Nothing else is folded — ``X-Distill-GGUF`` is a different
57
+ # model and never answers for ``X-GGUF`` (S3/F7). Lazy import keeps the
58
+ # config module's import graph unchanged.
59
+ from hugpy_engine.fit.types import key_equivalent
60
+ candidates = sorted(k for k in registry if key_equivalent(_bare(k), model_key))
53
61
  if not candidates:
54
62
  return None
55
63
  if len(candidates) == 1:
@@ -101,6 +109,14 @@ def get_model_path(key: str):
101
109
  return env_override
102
110
  cfg = get_model_config(key)
103
111
  path = os.path.join(MODELS_HOME,cfg.folder)
112
+ # A managed copy takes precedence. The worker's external pointer is used
113
+ # only when its Hugpy cache does not hold the model.
114
+ external = (getattr(cfg, "extra", None) or {}).get("external_location")
115
+ if external and not model_looks_downloaded(path, cfg) and (
116
+ os.path.isdir(external) or
117
+ ((getattr(cfg, "extra", None) or {}).get("ollama_model") and
118
+ os.path.isfile(external))):
119
+ return external
104
120
  # Read-through (2026-07-12 hotfix): cfg.folder is CENTRAL's layout — after
105
121
  # the store flattening it names the FLAT dir, but a worker's files may
106
122
  # still sit under a legacy task path. Without the resolver here, every
@@ -142,12 +142,18 @@ def model_meta(cfg: Any, *, vram_bytes: Optional[int] = None,
142
142
  framework = d.get("framework") or ""
143
143
  model_dir = _model_dir(d)
144
144
  dir_bytes = dir_size_bytes(model_dir) # whole dir (every file/variant)
145
+ # Host discovery may know the exact effective checkpoint size even though
146
+ # Central cannot read that worker's path (Ollama blobs and API-served
147
+ # vLLM checkpoints are common examples). Use that measured catalog value
148
+ # when no local directory is available; local GGUF resolution below still
149
+ # takes precedence when the weights are on Central.
150
+ discovered_size = d.get("effective_bytes") or d.get("size_bytes")
145
151
 
146
152
  # A GGUF repo often holds several quantizations but only ONE serves, so the
147
153
  # dir sum badly overstates "the model" — and it flowed straight into the VRAM
148
154
  # recommendation. Resolve the single effective quant (+ its mmproj) exactly as
149
155
  # the runner will, honoring the operator's gguf_file choice, and size by that.
150
- size = dir_bytes
156
+ size = dir_bytes or discovered_size
151
157
  gguf = {}
152
158
  if str(framework).lower() in ("gguf", "llama_cpp") and model_dir:
153
159
  try:
@@ -157,6 +163,8 @@ def model_meta(cfg: Any, *, vram_bytes: Optional[int] = None,
157
163
  gguf = {}
158
164
  if gguf.get("effective_bytes"):
159
165
  size = gguf["effective_bytes"]
166
+ elif discovered_size:
167
+ size = discovered_size
160
168
 
161
169
  selected = selected_err = None
162
170
  gguf_sel = str(select_gguf or "").strip()
@@ -177,7 +185,7 @@ def model_meta(cfg: Any, *, vram_bytes: Optional[int] = None,
177
185
  out = {
178
186
  "model_key": d.get("model_key"),
179
187
  "size_bytes": size, # effective quant for GGUF
180
- "dir_bytes": dir_bytes, # whole-dir footprint (all variants)
188
+ "dir_bytes": dir_bytes or (discovered_size if not model_dir else None),
181
189
  "quant": parse_quant(gguf.get("effective_gguf") or filename),
182
190
  "params_b": parse_params_b(name, hub_id, filename),
183
191
  "ctx_max": ctx_max,
@@ -16,6 +16,8 @@ refresh_registry() explicitly — e.g. on hugpy module startup.
16
16
 
17
17
  import json
18
18
  import os
19
+ import time
20
+ import hashlib
19
21
  from dataclasses import MISSING, fields
20
22
  from typing import Dict
21
23
  from abstract_essentials import get_logFile, safe_dump_to_file, safe_load_from_json
@@ -773,6 +775,7 @@ def derive_model_config_row(name, row):
773
775
  or row.get("tokenizer_model_max_length")
774
776
  or row.get("max_position_embeddings") or DEFAULT_MAX_TOKENS_LOCAL,
775
777
  "filename": row.get("filename"), "include": row.get("include"),
778
+ **({"ollama_model": row["ollama_model"]} if row.get("ollama_model") else {}),
776
779
  "port": row.get("port"), "host": row.get("host"),
777
780
  # SERVEABLE = at least one advertised task has a runner (k61). It used to
778
781
  # mean "EVERY task has one", which made a partially-servable row read as
@@ -813,6 +816,10 @@ def _absorb_disk(staple, disc):
813
816
  for k in ("dir", "folder", "filename"):
814
817
  if disc.get(k):
815
818
  staple[k] = disc[k]
819
+ for k in ("model_max_length", "tokenizer_model_max_length",
820
+ "max_position_embeddings"):
821
+ if disc.get(k) is not None:
822
+ staple[k] = disc[k]
816
823
 
817
824
  def merge_discovery_into_models(discovery, base=None):
818
825
  base = base if base is not None else MODELS
@@ -1492,6 +1499,8 @@ def get_models_dict(models_dict_path=None, dict_return=False, return_dict=False,
1492
1499
  # discovery/path (a fresh walk) forces a rebuild. During the import-time
1493
1500
  # build the globals aren't bound yet -> globals().get() is None -> build.
1494
1501
  if discovery is None and models_dict_path is None:
1502
+ if "_apply_worker_catalog" in globals():
1503
+ _apply_worker_catalog()
1495
1504
  cached = globals().get("MODEL_REGISTRY_DICT" if dict_return else "MODEL_REGISTRY")
1496
1505
  if cached:
1497
1506
  return cached
@@ -1536,6 +1545,171 @@ def get_models_dict(models_dict_path=None, dict_return=False, return_dict=False,
1536
1545
  MODEL_REGISTRY: Dict[str, ModelConfig] = get_models_dict()
1537
1546
  MODEL_REGISTRY_DICT: Dict[str, dict] = get_models_dict(dict_return=True)
1538
1547
 
1548
+ # A worker owns the path to its external weights. Central stores only the
1549
+ # catalog metadata and a per-worker path pointer; it must never resolve that
1550
+ # pointer as a path on Central or offer it as a central transfer source.
1551
+ _WORKER_CATALOG_PATH = os.path.join(os.path.dirname(MODELS_DISCOVERY_PATH),
1552
+ "worker_model_catalog.json")
1553
+ _WORKER_CATALOG_SEEN = 0.0
1554
+ _WORKER_ONLY_KEYS: set[str] = set()
1555
+ _WORKER_TOUCHED_KEYS: set[str] = set()
1556
+
1557
+
1558
+ def _central_copy_present(key: str) -> bool:
1559
+ """A registry row alone does not prove Central owns transferable bytes."""
1560
+ try:
1561
+ from hugpy_engine.config.main import get_model_path
1562
+ from hugpy_storage.model_presence import model_looks_downloaded
1563
+ cfg = MODEL_REGISTRY[key]
1564
+ return model_looks_downloaded(get_model_path(key), cfg)
1565
+ except Exception: # noqa: BLE001 — uncertain means no destructive cold reset
1566
+ return False
1567
+
1568
+
1569
+ def record_worker_models(worker_id: str, rows: dict) -> list[str]:
1570
+ """Persist one host scan, shared by all Central web processes."""
1571
+ import fcntl
1572
+ path = _WORKER_CATALOG_PATH
1573
+ os.makedirs(os.path.dirname(path), exist_ok=True)
1574
+ with open(path + ".lock", "a+") as lock:
1575
+ fcntl.flock(lock, fcntl.LOCK_EX)
1576
+ try:
1577
+ with open(path, encoding="utf-8") as fh:
1578
+ data = json.load(fh)
1579
+ except (OSError, ValueError):
1580
+ data = {}
1581
+ clean = {}
1582
+ for key, row in (rows or {}).items():
1583
+ if not isinstance(key, str) or not isinstance(row, dict):
1584
+ continue
1585
+ location = row.get("external_location")
1586
+ api_url = row.get("api_url")
1587
+ try:
1588
+ from urllib.parse import urlsplit
1589
+ parsed_api = urlsplit(api_url) if isinstance(api_url, str) else None
1590
+ is_api = bool(parsed_api and parsed_api.scheme == "http"
1591
+ and parsed_api.hostname in ("127.0.0.1", "localhost", "::1")
1592
+ and parsed_api.port and not parsed_api.username
1593
+ and not parsed_api.password and parsed_api.path in ("", "/")
1594
+ and not parsed_api.query and not parsed_api.fragment)
1595
+ except ValueError:
1596
+ is_api = False
1597
+ if not ((isinstance(location, str) and os.path.isabs(location)) or is_api):
1598
+ continue
1599
+ hub = str(row.get("hub_id") or "").strip("/").lower()
1600
+ current = MODEL_REGISTRY.get(key)
1601
+ if (not is_api and current is not None and
1602
+ str(current.hub_id or "").strip("/").lower() != hub):
1603
+ owner = str(row.get("hub_id") or "local").split("/", 1)[0]
1604
+ key = f"{owner}~{key}"
1605
+ current = MODEL_REGISTRY.get(key)
1606
+ if ((not is_api and current is not None and
1607
+ str(current.hub_id or "").strip("/").lower() != hub) or
1608
+ (key in clean and clean[key].get("worker_location") != location
1609
+ and clean[key].get("api_url") != api_url)):
1610
+ key += "~" + hashlib.sha256(location.encode()).hexdigest()[:8]
1611
+ clean[key] = {k: row.get(k) for k in (
1612
+ "name", "hub_id", "framework", "tasks", "primary_task",
1613
+ "filename", "model_max_length", "model_key", "size_bytes",
1614
+ "effective_bytes", "ollama_model", "api_url", "served_model",
1615
+ "service_kind") if row.get(k) is not None}
1616
+ clean[key]["model_key"] = key
1617
+ if isinstance(location, str) and os.path.isabs(location):
1618
+ clean[key]["worker_location"] = location
1619
+ old = (data.get(worker_id) or {}).get("models")
1620
+ changed = old != clean
1621
+ if changed:
1622
+ data[worker_id] = {"at": time.time(), "models": clean}
1623
+ tmp = path + f".{os.getpid()}.tmp"
1624
+ with open(tmp, "w", encoding="utf-8") as fh:
1625
+ json.dump(data, fh)
1626
+ os.replace(tmp, path)
1627
+ fcntl.flock(lock, fcntl.LOCK_UN)
1628
+ _apply_worker_catalog(force=changed)
1629
+ # The canonical keys may differ from worker-local scan keys when central
1630
+ # already has a same-named model from another hub. Return those identities
1631
+ # so heartbeat placement uses the same keys as the shared model catalog.
1632
+ return sorted(clean)
1633
+
1634
+
1635
+ def _apply_worker_catalog(force=False) -> None:
1636
+ """Overlay worker-only rows on the regular catalog, preserving local rows."""
1637
+ global _WORKER_CATALOG_SEEN
1638
+ registry = globals().get("MODEL_REGISTRY")
1639
+ registry_dict = globals().get("MODEL_REGISTRY_DICT")
1640
+ if registry is None or registry_dict is None:
1641
+ return # import-time base build
1642
+ try:
1643
+ stamp = os.stat(_WORKER_CATALOG_PATH).st_mtime_ns
1644
+ if not force and stamp == _WORKER_CATALOG_SEEN:
1645
+ return
1646
+ with open(_WORKER_CATALOG_PATH, encoding="utf-8") as fh:
1647
+ data = json.load(fh)
1648
+ except (OSError, ValueError):
1649
+ return
1650
+ locations: dict[str, dict[str, str]] = {}
1651
+ external_apis: dict[str, dict[str, dict]] = {}
1652
+ candidates: dict[str, dict] = {}
1653
+ for worker_id, entry in data.items():
1654
+ for key, row in (entry.get("models") or {}).items():
1655
+ if not isinstance(row, dict):
1656
+ continue
1657
+ if row.get("worker_location"):
1658
+ locations.setdefault(key, {})[worker_id] = row["worker_location"]
1659
+ if row.get("api_url"):
1660
+ external_apis.setdefault(key, {})[worker_id] = {
1661
+ field: row[field] for field in ("api_url", "served_model", "service_kind")
1662
+ if row.get(field) is not None}
1663
+ candidates.setdefault(key, row)
1664
+
1665
+ for key in list(_WORKER_ONLY_KEYS - candidates.keys()):
1666
+ registry.pop(key, None)
1667
+ registry_dict.pop(key, None)
1668
+ _WORKER_ONLY_KEYS.discard(key)
1669
+ for key in _WORKER_TOUCHED_KEYS - candidates.keys():
1670
+ if key in registry_dict:
1671
+ registry_dict[key].pop("worker_locations", None)
1672
+ registry_dict[key].pop("worker_external_apis", None)
1673
+ registry_dict[key].pop("worker_only", None)
1674
+ if key in registry:
1675
+ registry[key].extra.pop("worker_only", None)
1676
+ for key, row in candidates.items():
1677
+ if key not in registry:
1678
+ clean = {k: v for k, v in row.items() if k != "worker_location"}
1679
+ derived, _ = derive_model_config_row(key, clean)
1680
+ if not derived:
1681
+ continue
1682
+ derived["worker_only"] = True
1683
+ for size_key in ("size_bytes", "effective_bytes"):
1684
+ if row.get(size_key) is not None:
1685
+ derived[size_key] = row[size_key]
1686
+ cfg = get_assessed_model_config(derived)
1687
+ if not cfg:
1688
+ continue
1689
+ registry[key] = cfg
1690
+ registry_dict[key] = cfg.to_dict()
1691
+ for size_key in ("size_bytes", "effective_bytes"):
1692
+ if row.get(size_key) is not None:
1693
+ registry_dict[key][size_key] = row[size_key]
1694
+ _WORKER_ONLY_KEYS.add(key)
1695
+ worker_only = key in _WORKER_ONLY_KEYS or not _central_copy_present(key)
1696
+ registry_dict[key]["worker_locations"] = locations.get(key, {})
1697
+ if external_apis.get(key):
1698
+ registry_dict[key]["worker_external_apis"] = external_apis[key]
1699
+ else:
1700
+ registry_dict[key].pop("worker_external_apis", None)
1701
+ registry_dict[key]["worker_only"] = worker_only
1702
+ if worker_only:
1703
+ registry[key].extra["worker_only"] = True
1704
+ else:
1705
+ registry[key].extra.pop("worker_only", None)
1706
+ _WORKER_TOUCHED_KEYS.clear()
1707
+ _WORKER_TOUCHED_KEYS.update(candidates)
1708
+ _WORKER_CATALOG_SEEN = stamp
1709
+
1710
+
1711
+ _apply_worker_catalog(force=True)
1712
+
1539
1713
 
1540
1714
  def get_model_registry(dict_return=False, return_dict=False):
1541
1715
  dict_return = dict_return or return_dict
@@ -1569,6 +1743,8 @@ def refresh_registry(run_discovery=True):
1569
1743
  MODEL_REGISTRY_DICT.update(fresh_dict)
1570
1744
  for stale in [k for k in MODEL_REGISTRY_DICT if k not in fresh_dict]:
1571
1745
  MODEL_REGISTRY_DICT.pop(stale, None)
1746
+ _WORKER_ONLY_KEYS.clear() # fresh is the central base; recalculate ownership
1747
+ _apply_worker_catalog(force=True)
1572
1748
  try:
1573
1749
  from hugpy_engine.config.models.models_default import refresh_task_registries
1574
1750
  refresh_task_registries()
@@ -32,7 +32,10 @@ def format_prompt(messages=None, prompt=None) -> str:
32
32
  return json.dumps(messages, ensure_ascii=False, indent=2, default=str)
33
33
 
34
34
  # view state <- canonical status
35
- _VIEW_STATE = {"pending": "waiting", "processing": "waiting",
35
+ # Keep queue presentation aligned with the canonical job lifecycle. A request
36
+ # assigned to a worker but still pre-filling/loading is processing, not waiting;
37
+ # collapsing the two made a live worker request look orphaned in the console.
38
+ _VIEW_STATE = {"pending": "waiting", "processing": "processing",
36
39
  "streaming": "active"}
37
40
  _QUEUE_KINDS_EXCLUDED = {"download"}
38
41
 
@@ -92,6 +95,13 @@ def snapshot() -> list:
92
95
  "kind": d["kind"],
93
96
  "prompt": d.get("prompt") or "",
94
97
  "state": state,
98
+ # WHERE a processing call is (2026-09-29): "prefill" while the
99
+ # engine is still reading the prompt (n_past/n_prompt ride on
100
+ # progress/input_tokens), "" once tokens flow. Without this the
101
+ # queue read "answering, 0 tokens" for a 105 s legitimate prefill.
102
+ "stage": d.get("stage") or "",
103
+ "progress": d.get("progress"),
104
+ "input_tokens": d.get("input_tokens"),
95
105
  "elapsed": d["elapsed"],
96
106
  "wait": d["wait"],
97
107
  "tokens": d["tokens"],
@@ -105,6 +115,8 @@ def counts() -> dict:
105
115
  if d["kind"] not in _QUEUE_KINDS_EXCLUDED]
106
116
  waiting = sum(1 for d in live
107
117
  if _VIEW_STATE.get(d["status"]) == "waiting")
118
+ # Preserve the existing API shape (`active` is the non-waiting bucket),
119
+ # while snapshots distinguish `processing` from token-producing `active`.
108
120
  active = sum(1 for d in live
109
- if _VIEW_STATE.get(d["status"]) == "active")
121
+ if _VIEW_STATE.get(d["status"]) in ("processing", "active"))
110
122
  return {"waiting": waiting, "active": active, "total": waiting + active}
@@ -697,7 +697,25 @@ async def stream_runner(runner, req, cancel_event=None):
697
697
  timings=getattr(result, "timings", None))
698
698
  else:
699
699
  yield ErrorEvent(request_id=req.request_id,
700
- message=getattr(result, "error", None) or "run failed")
700
+ message=getattr(result, "error", None) or "run failed",
701
+ load_failure=(getattr(result, "load_failure", None)
702
+ if isinstance(getattr(result, "load_failure", None), dict)
703
+ else None))
704
+
705
+
706
+ def _rewrap_error_event(event, rid):
707
+ """The ONE re-wrap of an inner ErrorEvent under the outer request id
708
+ (S5, 2026-09-29). It used to rebuild the event from ``message`` alone, so
709
+ the worker's structured ``load_failure`` (class + fit_failure + refusal —
710
+ F3) and ``code`` were dropped HERE, on central, between the relay and the
711
+ /v1 envelope: the client saw ``type: api_error`` with no ``fit_failure``
712
+ although the worker had logged kind=vram_fit. Carry both."""
713
+ return ErrorEvent(request_id=rid,
714
+ message=getattr(event, "message", None) or "run failed",
715
+ load_failure=(getattr(event, "load_failure", None)
716
+ if isinstance(getattr(event, "load_failure", None), dict)
717
+ else None),
718
+ code=(str(getattr(event, "code")) if getattr(event, "code", None) else None))
701
719
 
702
720
 
703
721
  async def execute_prompt_stream(*args, cancel_event=None, **kwargs):
@@ -948,8 +966,7 @@ async def execute_chat_stream(*args, cancel_event=None, **kwargs):
948
966
  output_chunks=1, finish_reason="stop",
949
967
  usage=usage_totals, timings=timings_last)
950
968
  else:
951
- yield ErrorEvent(request_id=rid,
952
- message=getattr(event, "message", None) or "run failed")
969
+ yield _rewrap_error_event(event, rid)
953
970
  errored = True
954
971
  break
955
972
  else:
@@ -0,0 +1,28 @@
1
+ """``hugpy_engine.fit`` — the pure, deterministic evict-to-fit core.
2
+
3
+ Standalone: imports only ``hugpy_engine`` siblings (``eviction``, ``spill``)
4
+ and the standard library — no torch, no nvml, no fleet. See ``plan.py``.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from hugpy_engine.fit.flex import ( # noqa: F401
9
+ FlexPlan, PartialPlan, band_bounds, band_ceiling, band_floor,
10
+ ctx_band_bounds, flex_priority_key, kv_at_ctx_pct, leniency_floor_pct,
11
+ plan_explicit_offload, plan_flex, plan_partial_offload,
12
+ )
13
+ from hugpy_engine.fit.plan import evict_order, need_split, plan_fit # noqa: F401
14
+ from hugpy_engine.fit.types import ( # noqa: F401
15
+ FAILURE_KINDS, DeviceVram, Eviction, FitFailure, FitPlan, FitPolicy,
16
+ FitRequest, MoeSplit, Resident, ResourceSnapshot, VramSnapshot,
17
+ canonical_key, key_equivalent,
18
+ )
19
+
20
+ __all__ = [
21
+ "plan_fit", "evict_order", "need_split",
22
+ "ResourceSnapshot", "VramSnapshot", "DeviceVram", "FitPolicy", "Resident",
23
+ "FitRequest", "Eviction", "MoeSplit", "FitFailure", "FAILURE_KINDS", "FitPlan",
24
+ "canonical_key", "key_equivalent",
25
+ "FlexPlan", "PartialPlan", "band_bounds", "band_ceiling", "band_floor",
26
+ "ctx_band_bounds", "flex_priority_key", "kv_at_ctx_pct", "leniency_floor_pct",
27
+ "plan_explicit_offload", "plan_flex", "plan_partial_offload",
28
+ ]