mtplx 2.2.0__tar.gz → 2.3.0__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 (364) hide show
  1. {mtplx-2.2.0 → mtplx-2.3.0}/CHANGELOG.md +129 -0
  2. {mtplx-2.2.0 → mtplx-2.3.0}/PKG-INFO +3 -2
  3. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/artifacts.py +0 -1
  4. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/commands/public.py +22 -5
  5. mtplx-2.3.0/mtplx/constrained.py +499 -0
  6. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/context_copy.py +7 -2
  7. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/engine_session.py +68 -1
  8. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/generation.py +306 -31
  9. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/hf_loader.py +35 -2
  10. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/model_scheduler.py +17 -2
  11. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/mtp_patch.py +9 -2
  12. mtplx-2.3.0/mtplx/progress_heartbeat.py +23 -0
  13. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/server/dashboard_state.py +10 -2
  14. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/server/omlx_bridge/tool_calling.py +77 -1
  15. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/server/openai.py +589 -29
  16. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/session_bank.py +6 -2
  17. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/thermal.py +86 -22
  18. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/thermal_sidecar.py +12 -5
  19. mtplx-2.3.0/mtplx/thinking_guard.py +442 -0
  20. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/version.py +2 -2
  21. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx.egg-info/PKG-INFO +3 -2
  22. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx.egg-info/SOURCES.txt +9 -0
  23. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx.egg-info/requires.txt +2 -1
  24. {mtplx-2.2.0 → mtplx-2.3.0}/pyproject.toml +6 -2
  25. mtplx-2.3.0/scripts/session_forensics.py +439 -0
  26. mtplx-2.3.0/scripts/walltime-lab/run_project.py +227 -0
  27. mtplx-2.3.0/tests/test_constrained.py +646 -0
  28. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_context_copy_stats.py +107 -0
  29. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_dashboard_endpoints.py +15 -0
  30. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_hf_loader.py +34 -0
  31. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_model_scheduler.py +25 -0
  32. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_mtp_patch.py +72 -0
  33. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_no_mlx_imports.py +78 -19
  34. mtplx-2.3.0/tests/test_omlx_bridge.py +292 -0
  35. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_openai_bridge.py +6 -4
  36. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_postcommit_wait_integration.py +58 -0
  37. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_server_openai.py +289 -7
  38. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_session_bank.py +22 -0
  39. mtplx-2.3.0/tests/test_stream_stall_watchdog.py +66 -0
  40. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_thermal.py +61 -0
  41. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_thermal_sidecar.py +25 -0
  42. mtplx-2.3.0/tests/test_thinking_guard.py +275 -0
  43. mtplx-2.3.0/tests/test_tool_nested_args_streaming.py +363 -0
  44. mtplx-2.2.0/tests/test_omlx_bridge.py +0 -139
  45. {mtplx-2.2.0 → mtplx-2.3.0}/CITATION.cff +0 -0
  46. {mtplx-2.2.0 → mtplx-2.3.0}/LICENSE +0 -0
  47. {mtplx-2.2.0 → mtplx-2.3.0}/MANIFEST.in +0 -0
  48. {mtplx-2.2.0 → mtplx-2.3.0}/NOTICE +0 -0
  49. {mtplx-2.2.0 → mtplx-2.3.0}/README.md +0 -0
  50. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/__init__.py +0 -0
  51. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/adaptive.py +0 -0
  52. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/app_settings.py +0 -0
  53. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/attention_context.py +0 -0
  54. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/attention_split.py +0 -0
  55. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/backends/__init__.py +0 -0
  56. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/backends/deepseek_mtp.py +0 -0
  57. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/backends/descriptors.py +0 -0
  58. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/backends/gemma4_assistant.py +0 -0
  59. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/backends/glm_mtp.py +0 -0
  60. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/backends/hy_v3_mtp.py +0 -0
  61. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/backends/mimo_mtp.py +0 -0
  62. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/backends/nemotron_h_mtp.py +0 -0
  63. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/backends/qwen3_next.py +0 -0
  64. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/backends/registry.py +0 -0
  65. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/backends/step3p5_mtp.py +0 -0
  66. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/batching/__init__.py +0 -0
  67. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/batching/admission.py +0 -0
  68. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/batching/buckets.py +0 -0
  69. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/batching/scheduler.py +0 -0
  70. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/batching/state.py +0 -0
  71. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/__init__.py +0 -0
  72. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/prompts/aime_2026.jsonl +0 -0
  73. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/prompts/calibration_coding.jsonl +0 -0
  74. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/prompts/default.jsonl +0 -0
  75. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/prompts/flappy.jsonl +0 -0
  76. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/prompts/long_code.jsonl +0 -0
  77. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/prompts/long_code_uncapped.jsonl +0 -0
  78. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/prompts/python_modules_long.jsonl +0 -0
  79. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/__init__.py +0 -0
  80. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/aime.py +0 -0
  81. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/batch_equivalence.py +0 -0
  82. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/capture_commit_equivalence.py +0 -0
  83. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/competitor_baselines.py +0 -0
  84. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/contract_probe.py +0 -0
  85. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/harness.py +0 -0
  86. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/mtp1_gate.py +0 -0
  87. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/mtp1_sampler_smoke.py +0 -0
  88. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/mtp_adaptive.py +0 -0
  89. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/mtp_chain_probe.py +0 -0
  90. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/mtp_depth_grid.py +0 -0
  91. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/mtp_depth_sweep.py +0 -0
  92. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/mtp_tree_probe.py +0 -0
  93. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/multi_qmv_probe.py +0 -0
  94. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/preflight.py +0 -0
  95. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/runtime_smoke.py +0 -0
  96. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/session_bank.py +0 -0
  97. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/truth.py +0 -0
  98. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/verify_profile.py +0 -0
  99. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/verify_qmm_probe.py +0 -0
  100. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/runners/verify_ratio.py +0 -0
  101. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/schema.py +0 -0
  102. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/validators/__init__.py +0 -0
  103. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/validators/aime.py +0 -0
  104. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/benchmarks/validators/basic.py +0 -0
  105. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/block_attention.py +0 -0
  106. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/cache_bank/__init__.py +0 -0
  107. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/cache_bank/codec.py +0 -0
  108. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/cache_bank/cold_tier.py +0 -0
  109. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/cache_state.py +0 -0
  110. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/chat_encoding.py +0 -0
  111. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/cli.py +0 -0
  112. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/commands/__init__.py +0 -0
  113. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/commands/forge.py +0 -0
  114. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/compressed_tensors.py +0 -0
  115. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/config.py +0 -0
  116. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/constants.py +0 -0
  117. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/correctors/__init__.py +0 -0
  118. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/correctors/diagonal_affine.py +0 -0
  119. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/correctors/low_rank.py +0 -0
  120. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/daemon_client.py +0 -0
  121. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/dashboard/__init__.py +0 -0
  122. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/dashboard/_static/assets/index-BYd4MFty.css +0 -0
  123. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/dashboard/_static/assets/index-COqTDxL-.js +0 -0
  124. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/dashboard/_static/index.html +0 -0
  125. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/deepseek_mtp_patch.py +0 -0
  126. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/default_models.py +0 -0
  127. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/diagnostics.py +0 -0
  128. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/draft_lm_head.py +0 -0
  129. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/draft_sampling.py +0 -0
  130. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/env.py +0 -0
  131. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/errors.py +0 -0
  132. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/expert_layout.py +0 -0
  133. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/fan_mode.py +0 -0
  134. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/fast_sampling.py +0 -0
  135. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/gdn_capture.py +0 -0
  136. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/gemma4_pair.py +0 -0
  137. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/glm_mtp_patch.py +0 -0
  138. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/graphbank.py +0 -0
  139. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/hardware.py +0 -0
  140. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/hy_v3_mtp_patch.py +0 -0
  141. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernel_selfcheck.py +0 -0
  142. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernels/__init__.py +0 -0
  143. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernels/copy_leaf.py +0 -0
  144. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernels/fused_norm.py +0 -0
  145. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernels/lm_head_topk.py +0 -0
  146. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernels/logits_topk.py +0 -0
  147. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernels/native_gdn_tail.py +0 -0
  148. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernels/sdpa_2pass.py +0 -0
  149. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernels/sdpa_2pass_paged.py +0 -0
  150. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernels/sdpa_2pass_paged_q8.py +0 -0
  151. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernels/sdpa_gqa_packed.py +0 -0
  152. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kernels/verify_mlp_fused.py +0 -0
  153. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kpi/__init__.py +0 -0
  154. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kpi/reference_vllm.py +0 -0
  155. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kpi/runtime_kpis.py +0 -0
  156. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/kv_quant.py +0 -0
  157. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/loop_guard.py +0 -0
  158. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/mimo_mtp_patch.py +0 -0
  159. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/model_catalog.py +0 -0
  160. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/mtp_activation_stats.py +0 -0
  161. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/mtp_adapters.py +0 -0
  162. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/native_mlp.py +0 -0
  163. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/nax_verify.py +0 -0
  164. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/nemotron_h_mtp_patch.py +0 -0
  165. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/opencode.py +0 -0
  166. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/pi.py +0 -0
  167. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/prefill_bench.py +0 -0
  168. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/profiles.py +0 -0
  169. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/proposal_reranker.py +0 -0
  170. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/qwen3_5_mtp_patch.py +0 -0
  171. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/reasoning_codecs.py +0 -0
  172. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/runtime.py +0 -0
  173. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/runtime_options.py +0 -0
  174. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/sampling.py +0 -0
  175. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/server/__init__.py +0 -0
  176. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/server/omlx_bridge/__init__.py +0 -0
  177. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/server/omlx_bridge/adapter.py +0 -0
  178. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/server/omlx_bridge/thinking.py +0 -0
  179. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/server_urls.py +0 -0
  180. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/speculative.py +0 -0
  181. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/step3p5_mtp_patch.py +0 -0
  182. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/swival.py +0 -0
  183. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/templates/qwen36_froggeric_v19/chat_template.jinja +0 -0
  184. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/templates/qwen36_froggeric_v21_3/chat_template.jinja +0 -0
  185. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/trace_parity.py +0 -0
  186. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/turboquant.py +0 -0
  187. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/ui/__init__.py +0 -0
  188. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/ui/banner.py +0 -0
  189. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/ui/chat_printer.py +0 -0
  190. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/ui/download_progress.py +0 -0
  191. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/ui/onboarding.py +0 -0
  192. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/ui/panels.py +0 -0
  193. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/ui/progress.py +0 -0
  194. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/verify_kernels.py +0 -0
  195. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/verify_qmv.py +0 -0
  196. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/vision/__init__.py +0 -0
  197. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/vision/processing.py +0 -0
  198. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/vision/qwen3_vl_tower.py +0 -0
  199. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx/vision/splice.py +0 -0
  200. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx.egg-info/dependency_links.txt +0 -0
  201. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx.egg-info/entry_points.txt +0 -0
  202. {mtplx-2.2.0 → mtplx-2.3.0}/mtplx.egg-info/top_level.txt +0 -0
  203. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/agent_user_path_qa.py +0 -0
  204. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/aime_serve_gate.py +0 -0
  205. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/aime_shape_memory_bench.py +0 -0
  206. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/collect_mtp_activation_stats.py +0 -0
  207. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/collect_mtp_hidden_calib.py +0 -0
  208. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/combine_mtp_adapters.py +0 -0
  209. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/compiled_verify_exactness.py +0 -0
  210. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/convert_step37_to_mtplx_step3p5.py +0 -0
  211. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/eval_mtp_corrector.py +0 -0
  212. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/filter_mtp_adapter.py +0 -0
  213. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/fp16_turbo_exactness_20260707.py +0 -0
  214. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/fresh_venv_smoke.sh +0 -0
  215. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/hygiene_scan.sh +0 -0
  216. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/install_macos.sh +0 -0
  217. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/install_preview_global.sh +0 -0
  218. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/kvcache_exactness_audit_20260703.py +0 -0
  219. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/kvcache_soak_20260703.py +0 -0
  220. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/kvcache_warm_probe_20260703.py +0 -0
  221. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/make_fp16_precision_sibling.py +0 -0
  222. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/midform_gate.py +0 -0
  223. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/ocspeed-20260703/abba_final.sh +0 -0
  224. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/ocspeed-20260703/accept_depth_probe.py +0 -0
  225. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/ocspeed-20260703/arm_runner.sh +0 -0
  226. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/ocspeed-20260703/forward_depth_bisect.py +0 -0
  227. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/ocspeed-20260703/longgen_probe.py +0 -0
  228. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/ocspeed-20260703/loop_degradation_probe.py +0 -0
  229. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/ocspeed-20260703/paired_longgen.sh +0 -0
  230. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/ocspeed-20260703/sdpa_microbench.py +0 -0
  231. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/ocspeed-20260703/snapshot_tail.py +0 -0
  232. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/opencode_concurrency_qa.py +0 -0
  233. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/phase0h_paged_verifier_exactness.py +0 -0
  234. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/pillar_gate_qa.py +0 -0
  235. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/probe_mlx_pr3026_qsdpa.py +0 -0
  236. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/probe_mx_compile_buckets.py +0 -0
  237. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/probe_paged_gqa_sdpa_routes.py +0 -0
  238. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/prodqa-20260703/candidate_truecold_20260703.py +0 -0
  239. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/prodqa-20260703/cold_pair_recheck.py +0 -0
  240. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/prodqa-20260703/competitors_20260703.sh +0 -0
  241. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/prodqa-20260703/decode_gap_matrix.py +0 -0
  242. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/prodqa-20260703/hermes_pty_driver.py +0 -0
  243. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/prodqa-20260703/omlx_probe.py +0 -0
  244. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/prodqa-20260703/pi_pty_driver.py +0 -0
  245. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/prodqa-20260703/pillar_ab_20260703.py +0 -0
  246. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/prodqa-20260703/tool_gauntlet_20260703.sh +0 -0
  247. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/quality_fp16_parent_logitdiff_20260707.py +0 -0
  248. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/r1_chisquare_verifier_correctness.py +0 -0
  249. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/release_macos_v1.sh +0 -0
  250. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/run_context_degradation_diagnostics.py +0 -0
  251. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/serve_openai_mtplx.py +0 -0
  252. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/session_cache_followup_qa.py +0 -0
  253. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/sparkle_rehearsal_kit.sh +0 -0
  254. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/step_acceptance.py +0 -0
  255. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/step_smoke.py +0 -0
  256. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/train_mtp_adapter_c4.py +0 -0
  257. {mtplx-2.2.0 → mtplx-2.3.0}/scripts/validate_step_mtp_injector.py +0 -0
  258. {mtplx-2.2.0 → mtplx-2.3.0}/setup.cfg +0 -0
  259. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_adaptive.py +0 -0
  260. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_aime_serve_gate.py +0 -0
  261. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_ar_batch_penalties.py +0 -0
  262. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_artifacts.py +0 -0
  263. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_attention_split.py +0 -0
  264. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_background_warmup.py +0 -0
  265. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_batching_foundation.py +0 -0
  266. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_cache_bank.py +0 -0
  267. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_cache_state.py +0 -0
  268. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_cli_parity_tools.py +0 -0
  269. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_cold_tier_write_budget.py +0 -0
  270. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_compressed_tensors.py +0 -0
  271. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_config.py +0 -0
  272. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_config_profile_precedence.py +0 -0
  273. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_context_degradation_profiles.py +0 -0
  274. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_correctors.py +0 -0
  275. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_daemon_client.py +0 -0
  276. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_default_models.py +0 -0
  277. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_device_draft_core.py +0 -0
  278. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_diagnostics.py +0 -0
  279. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_download_progress.py +0 -0
  280. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_draft_lm_head.py +0 -0
  281. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_engine_session_concurrency.py +0 -0
  282. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_engine_session_env.py +0 -0
  283. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_fast_sampling.py +0 -0
  284. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_forge_cli.py +0 -0
  285. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_gdn_boundary_retention.py +0 -0
  286. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_generation_store_on_prefill.py +0 -0
  287. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_generation_sustained.py +0 -0
  288. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_graphbank_compiled_verify.py +0 -0
  289. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_hy_v3_mtp_backend.py +0 -0
  290. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_hygiene_scan.py +0 -0
  291. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_idle_postcommit_subagent.py +0 -0
  292. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_kernel_selfcheck.py +0 -0
  293. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_lazy_snapshot_cow.py +0 -0
  294. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_loop_guard.py +0 -0
  295. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_max_idle_watchdog.py +0 -0
  296. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_max_lifecycle.py +0 -0
  297. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_memory_pressure_guard.py +0 -0
  298. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_metal_memory_caps.py +0 -0
  299. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_midform_gate.py +0 -0
  300. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_model_catalog.py +0 -0
  301. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_mtp_activation_stats.py +0 -0
  302. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_mtp_adapters.py +0 -0
  303. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_mtp_alias_load_path.py +0 -0
  304. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_mtp_depth_sweep.py +0 -0
  305. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_nax_verify.py +0 -0
  306. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_onboarding.py +0 -0
  307. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_opencode.py +0 -0
  308. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_orphan_tool_markup.py +0 -0
  309. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_penalties.py +0 -0
  310. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_penalty_request_wiring.py +0 -0
  311. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_persistent_replay.py +0 -0
  312. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_phase0h_paged_verifier_exactness.py +0 -0
  313. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_policy_fingerprint_stability.py +0 -0
  314. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_postcommit_prefix_reuse.py +0 -0
  315. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_postcommit_resolve_for_request.py +0 -0
  316. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_postcommit_tools_plumbing.py +0 -0
  317. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_postcommit_wait.py +0 -0
  318. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_prefill_bench.py +0 -0
  319. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_prefill_chunk_defaults.py +0 -0
  320. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_prefill_tps_regression.py +0 -0
  321. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_profiles.py +0 -0
  322. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_prompt_encoding.py +0 -0
  323. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_public_cli.py +0 -0
  324. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_qwen3_5_mtp_backend.py +0 -0
  325. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_reasoning_stream_split.py +0 -0
  326. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_runtime_kpis.py +0 -0
  327. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_sampling.py +0 -0
  328. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_scoped_reasoning_history.py +0 -0
  329. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_sdpa_gqa_packed.py +0 -0
  330. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_session_bank_env_caps.py +0 -0
  331. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_ssd_boundary_repersist.py +0 -0
  332. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_step3p5_mtp_patch.py +0 -0
  333. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_sustained_long_context_qa.py +0 -0
  334. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_tool_aware_stream_translator.py +0 -0
  335. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_trace_parity.py +0 -0
  336. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_turboquant.py +0 -0
  337. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_turboquant_fallback.py +0 -0
  338. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_ui_progress.py +0 -0
  339. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_validators.py +0 -0
  340. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_vision_session_cache.py +0 -0
  341. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_vision_tower.py +0 -0
  342. {mtplx-2.2.0 → mtplx-2.3.0}/tests/test_vllm_reference.py +0 -0
  343. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/__init__.py +0 -0
  344. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/README.md +0 -0
  345. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/__init__.py +0 -0
  346. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/build.py +0 -0
  347. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/constants.py +0 -0
  348. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v1/copy_blocks.metal +0 -0
  349. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v1/float8.metal +0 -0
  350. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v1/gather_kv_cache.metal +0 -0
  351. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v1/kv_scale_update.metal +0 -0
  352. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v1/pagedattention.metal +0 -0
  353. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v1/reshape_and_cache.metal +0 -0
  354. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v1/utils.metal +0 -0
  355. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v2/copy_blocks.metal +0 -0
  356. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v2/float8.metal +0 -0
  357. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v2/gather_kv_cache.metal +0 -0
  358. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v2/gdn_linear_attention.metal +0 -0
  359. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v2/kv_scale_update.metal +0 -0
  360. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v2/pagedattention.metal +0 -0
  361. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v2/reshape_and_cache.metal +0 -0
  362. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v2/turboquant.metal +0 -0
  363. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/kernels_v2/utils.metal +0 -0
  364. {mtplx-2.2.0 → mtplx-2.3.0}/vllm_metal/metal/paged_ops.cpp +0 -0
@@ -4,6 +4,135 @@ All notable user-facing changes to MTPLX. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [2.3.0] - 2026-07-21
8
+
9
+ The agent reliability release: the #170 tool-argument collapse is
10
+ root-caused and fixed, structured output ships with full speculative
11
+ speed (community-contributed), agent sessions keep their warm cache
12
+ through client history rewrites, and eight findings from an independent
13
+ source review of v2.2.0 are fixed, each with a regression test that
14
+ fails on the pre-fix code.
15
+
16
+ ### Fixed
17
+
18
+ - Agent tool calls with nested arguments no longer collapse to empty `{}`
19
+ arguments (#170). The streaming Qwen-XML tool parser silently discarded
20
+ function-body text that was not wrapped in `<parameter=>` blocks, so a
21
+ model that wrote its arguments as a JSON object inside the
22
+ `<function=...>` envelope — the common slip on nested `edits` arrays —
23
+ produced a schema-valid call with empty arguments on requiredless tools
24
+ and a silently vanished call otherwise. Both the streaming and the final
25
+ parser now accept a pure JSON-object function body as the arguments
26
+ payload, and every unwrapped, lead, or trailing text lane is a loud
27
+ protocol fallback with identical contracts in the two parsers.
28
+ - The injected tool contract no longer shows degenerate `[]` examples for
29
+ array parameters: exemplars are populated from the item schema's own
30
+ keys (an `edits` array renders as
31
+ `[{"search": "ARGUMENT_VALUE", "replace": "ARGUMENT_VALUE"}]`), removing
32
+ an in-prompt template for empty-array emissions.
33
+ - The final tool-call extraction lane (non-stream parsing and the stream
34
+ fallback) no longer fabricates `{}`-argument calls out of function bodies
35
+ it could not read — live-reproduced on v2.2.0 as a `grep` call arriving
36
+ with empty arguments. All extraction dialects now share the strict
37
+ parsers' contract: a pure JSON-object function body is the arguments
38
+ payload, an unreadable body stays visible content, blank-body no-argument
39
+ calls still parse, and partial arguments are delivered exactly as the
40
+ model wrote them (schema validation remains the client's job, per the
41
+ OpenAI protocol).
42
+ - Context-copy speculative rounds now stop accepting at the first
43
+ accepted stop token, exactly like the MTP acceptance loop. Previously
44
+ the copy lane could accept an entire block past a stop, leaving the
45
+ target cache, logits/hidden selection, and MTP history advanced beyond
46
+ the emitted response while the final state was still marked safe to
47
+ commit — a session-cache poisoning risk on recurrent (GDN) models.
48
+ - Context-copy is prompt-only again at the boundary: proposal blocks are
49
+ sliced from the prompt and capped at its edge instead of running into
50
+ the model's own generated output (the self-repetition case the feature
51
+ contract excludes). Boundary-less candidates are skipped inside the
52
+ n-gram index so the best *valid* match still fires.
53
+ - Non-divisor packed MTP quantization widths (5-bit, 3-bit, 6-bit) now
54
+ infer the correct group size via total-bit arithmetic (#182; fix by
55
+ @Jonathangadeaharder in #183). A 5-bit group-64 head previously
56
+ inferred group 60, overwrote the artifact's correct declared contract,
57
+ and made the model unloadable.
58
+ - Long-running daemons no longer accumulate unbounded telemetry: the
59
+ SessionBank eviction log is a bounded ring, the scheduler's
60
+ started-by-batch-key counter collapses per-session key suffixes to
61
+ stable classes, the dashboard per-session TPS map is LRU-bounded, and
62
+ the OpenCode title fast path trims request metrics like every other
63
+ lane (#145-adjacent slow-creep hygiene).
64
+ - The desktop Hugging Face probe now recognizes rootless assistant-pair
65
+ bundles (`mtplx_pair.json` + `target/config.json`) the same way the
66
+ Python runtime does, so the official Gemma4 pair repos classify as
67
+ ready to download instead of unreadable (#107); Forge routes them to
68
+ install-instead-of-rebuild.
69
+ - `mtplx doctor --json` stdout is now guaranteed machine-parseable: the
70
+ report is built with stdout routed to stderr, so third-party lazy
71
+ importers that print() their errors (huggingface_hub does, whenever its
72
+ HTTP dependencies are broken or split across install locations) can no
73
+ longer prefix the JSON document with prose and break `--json` consumers.
74
+ - Agent tool-loop sessions no longer lose their warm cache when the
75
+ client rewrites history (transcript compaction, retroactive tool-result
76
+ digests): a common-prefix identity fallback (≥4096 shared tokens and
77
+ ≥25% of the prompt) keeps the session id instead of minting an
78
+ anonymous one, ending the cold full-context re-prefills those
79
+ rotations caused mid-session.
80
+ - Fan restore goes through the ThermalForge daemon socket instead of the
81
+ app-killing CLI path, and `mtplx pull` detects interrupted downloads
82
+ as incomplete instead of treating them as ready (thanks @titan550,
83
+ #178/#179/#180/#181).
84
+
85
+ ### Changed
86
+
87
+ - `parallel_tool_calls` is now honored: a request that declares it gets
88
+ the declared behavior in both directions (false → at most one tool
89
+ call per turn), with the previous client-profile heuristic kept only
90
+ as the fallback when the field is absent (thanks @PhilipJohnBasile,
91
+ #190 — Android Studio declares false today and was being ignored).
92
+ - Agent tool contract v13: instructs whole-file reads (the old
93
+ "smallest read range" clause provoked storms of 1-to-5-line
94
+ micro-reads) and forbids echoing file contents into visible text
95
+ (double-emitted file bodies inflated agent turns by tens of
96
+ thousands of characters). Measured on live agent sessions: thinking
97
+ share of generated tokens 75% → 6-29%, double emissions eliminated.
98
+
99
+ ### Added
100
+
101
+ - Structured output: `response_format` `json_object` and `json_schema`
102
+ are now enforced with llguidance token masks instead of silently
103
+ ignored — and the grammar composes with the MTP verify loop, so
104
+ constrained requests keep full speculative speed (measured: decode
105
+ parity within noise, under 5ms total mask cost per request). On
106
+ thinking templates a prelude grammar lets reasoning finish before the
107
+ document is forced. Opt-in strict tool-call grammars
108
+ (`MTPLX_TOOL_CALL_STRICT=1`) force every tool call to a declared tool
109
+ name with schema-valid arguments — a real OpenCode session built a
110
+ complete project through the strict lane with zero malformed calls.
111
+ Thanks @PhilipJohnBasile (#186, #187, #188). Requires the `[server]`
112
+ extra, which the desktop app installs by default; bare pip installs
113
+ get a clear 400 with an install hint.
114
+ - Durable per-request telemetry: `--request-log-jsonl` (env
115
+ `MTPLX_REQUEST_LOG_JSONL`) appends every request record as one JSON
116
+ line, and `scripts/session_forensics.py` correlates that log with an
117
+ OpenCode database into a single timeline with detectors for re-prefill
118
+ rewinds, TTFT stalls, thinking marathons, double emissions,
119
+ session-identity rotations, and usage mismatches.
120
+ - An **opt-in** agent-lane reasoning budget (`--agent-thinking-budget`,
121
+ env `MTPLX_THINKING_BUDGET`; OFF by default): at the budget the
122
+ reasoning segment is force-closed with a visible bridge so the turn
123
+ proceeds to its answer or tool call, and every engagement is surfaced
124
+ per-request in telemetry (`thinking_guard`). Below the budget decoding
125
+ is bit-exact; plain chat is never touched.
126
+ - Stream stall watchdog (#86 containment): if a stream receives nothing
127
+ while the model owner's progress heartbeat is frozen for
128
+ `MTPLX_STREAM_STALL_DEADLINE_S` (default 300s, 0 disables), the
129
+ request fails with a structured, diagnosable error and releases its
130
+ slot instead of hanging forever. Healthy long prefills and model loads
131
+ tick the heartbeat and are never affected; the daemon is never killed.
132
+ - `pip install mtplx` now includes Pillow, matching the advertised image
133
+ support in the app and server (#103); previously vision failed at
134
+ import unless the `[server]` extra was installed.
135
+
7
136
  ## [2.2.0] - 2026-07-19
8
137
 
9
138
  The copy-drafting and small-Mac release. Decoding: context-copy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mtplx
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Native MTP speculative decoding for Qwen3-Next on Apple Silicon.
5
5
  Author-email: Youssof Altoukhi <business@youssofal.com>
6
6
  License-Expression: Apache-2.0
@@ -25,6 +25,7 @@ License-File: LICENSE
25
25
  License-File: NOTICE
26
26
  Requires-Dist: fastapi>=0.136
27
27
  Requires-Dist: huggingface-hub>=0.36
28
+ Requires-Dist: pillow>=10
28
29
  Requires-Dist: mlx<0.33,>=0.31; sys_platform == "darwin" and platform_machine == "arm64"
29
30
  Requires-Dist: mlx-lm<0.32,>=0.31; sys_platform == "darwin" and platform_machine == "arm64"
30
31
  Requires-Dist: transformers!=5.13.0,<5.14; sys_platform == "darwin" and platform_machine == "arm64"
@@ -38,7 +39,7 @@ Provides-Extra: competitors
38
39
  Requires-Dist: dflash-mlx==0.1.0; extra == "competitors"
39
40
  Provides-Extra: server
40
41
  Requires-Dist: fastapi>=0.136; extra == "server"
41
- Requires-Dist: pillow>=10; extra == "server"
42
+ Requires-Dist: llguidance>=1.7; extra == "server"
42
43
  Requires-Dist: uvicorn>=0.46; extra == "server"
43
44
  Provides-Extra: dev
44
45
  Requires-Dist: build>=1.2; extra == "dev"
@@ -811,7 +811,6 @@ def _inspect_hf_model(repo_id: str) -> ModelInspection:
811
811
  config = dict(target_config)
812
812
  config["assistant_pair_bundle"] = pair_manifest
813
813
  config["mtplx_pair.json"] = True
814
- config_path = target_path
815
814
  config_error = None
816
815
  if config is None:
817
816
  raise RuntimeError(
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import contextlib
5
6
  import hashlib
6
7
  import json
7
8
  import os
@@ -1828,6 +1829,22 @@ class _temporary_env:
1828
1829
 
1829
1830
 
1830
1831
  def cmd_doctor(args: Any) -> int:
1832
+ # --json promises machine-parseable stdout. Probes import third-party
1833
+ # packages whose lazy loaders print() import errors straight to stdout
1834
+ # (huggingface_hub does, and a split/partially broken install makes it
1835
+ # certain), which used to prefix the JSON document with prose and break
1836
+ # every consumer. Build the whole report with stdout routed to stderr,
1837
+ # then emit only the document.
1838
+ if getattr(args, "json", False):
1839
+ with contextlib.redirect_stdout(sys.stderr):
1840
+ report = _build_doctor_report(args)
1841
+ _print(report)
1842
+ return 0
1843
+ report = _build_doctor_report(args)
1844
+ return _render_doctor_report(args, report)
1845
+
1846
+
1847
+ def _build_doctor_report(args: Any) -> dict[str, Any]:
1831
1848
  env = collect_environment(args.project_root).to_dict()
1832
1849
  from mtplx.hf_loader import hf_cache_report
1833
1850
  from mtplx.thermal import detect_thermal_control
@@ -1886,9 +1903,11 @@ def cmd_doctor(args: Any) -> int:
1886
1903
  output_dir=getattr(args, "output_dir", None),
1887
1904
  include_paths=bool(getattr(args, "include_paths", False)),
1888
1905
  )
1889
- if getattr(args, "json", False):
1890
- _print(report)
1891
- elif getattr(args, "summary", False):
1906
+ return report
1907
+
1908
+
1909
+ def _render_doctor_report(args: Any, report: dict[str, Any]) -> int:
1910
+ if getattr(args, "summary", False):
1892
1911
  diagnostics = report["diagnostics"]
1893
1912
  print(f"MTPLX doctor: {diagnostics['overall']}")
1894
1913
  for check in diagnostics["checks"]:
@@ -2066,7 +2085,6 @@ def cmd_stop_public(args: Any) -> int:
2066
2085
  """Stop a running MTPLX server via its health-reported pid."""
2067
2086
 
2068
2087
  from mtplx.daemon_client import (
2069
- DAEMON_PROBE_PORTS,
2070
2088
  probe_running_daemons,
2071
2089
  stop_daemon,
2072
2090
  )
@@ -10420,7 +10438,6 @@ def _quickstart_openwebui_payload(
10420
10438
  port = int(getattr(args, "port", 8000))
10421
10439
  model_id = _public_model_id_for_args(args, str(getattr(args, "model", "")))
10422
10440
  base = f"http://{_connect_host_for_bind(host)}:{port}"
10423
- profile = str(getattr(args, "profile", None) or DEFAULT_PROFILE_NAME)
10424
10441
  context_window = _inspection_context_window(inspection)
10425
10442
  return {
10426
10443
  "integration": "openwebui",