MTPLX 2.3.0

Released 2026-07-21. This is the agent reliability release. The tool-call argument corruption that plagued coding agents is root-caused and fixed, structured output arrives with full speculative speed, agent sessions stop wasting time on re-prefills and runaway reasoning, and a batch of hardening from an independent source review closes real correctness holes. Much of this release is community work: reported, diagnosed, and in several cases authored by contributors. Thank you.

Numbering note: this cycle was staged as 2.2.1, but it grew well past a patch. If you are on 2.2.0, everything below is new.

Structured output (#186, #187, #188)

  • response_format with json_object or json_schema is now enforced, not silently ignored. Grammar masks from llguidance constrain decoding so the response is valid JSON matching your schema. Designed and contributed by @PhilipJohnBasile across three staged PRs.
  • The grammar composes with MTP speculative decoding instead of turning it off. Constrained requests keep full speed: decode parity is within noise of unconstrained runs and the total masking cost is under five milliseconds per request. We verified the sampling math end to end, including a distributional check of the masked law through the real sampler.
  • On thinking models the grammar allows reasoning to finish before the document is forced. Practical note: reasoning can consume your max_tokens before the document starts, so for tight budgets pair structured output with --reasoning off or a larger budget.
  • Opt-in strict tool calls: launch with MTPLX_TOOL_CALL_STRICT=1 and every tool call is grammar-forced to a declared tool name with schema-valid JSON arguments. A real OpenCode session built a complete project through the strict lane with zero malformed calls.
  • Structured output needs the llguidance package, which ships with the [server] extra and inside the desktop app. A bare pip install mtplx returns a clear 400 with an install hint if you request it.

Tool calls that hold together (#170, #190)

  • Fixed the intermittent collapse of tool-call arguments to {} on Qwen3.6, the bug behind failed edits in OpenCode and similar agents (about one in ten edit turns). The streaming parser silently discarded function-body text that was not wrapped in parameter tags, and a second lane fabricated empty-argument calls from bodies it could not read. Both parsers now share one strict contract, and a model that writes its arguments as a plain JSON object inside the tool envelope gets its arguments delivered intact. Validated with a 168-turn live A/B: zero collapses, where the old code reproduced them. Thanks @druide67 for the report and the frequency estimate that matched our measurements.
  • The injected tool contract no longer shows empty [] examples for array parameters, which was teaching models the exact degenerate shape we did not want.
  • parallel_tool_calls is honored. Declare false and you get at most one tool call per turn; the old client-name heuristic now only applies when the field is absent. Contributed by @PhilipJohnBasile. Android Studio declares this field today and was being ignored.

Agent sessions and wall time

  • Agent turns got dramatically leaner. The injected tool contract now instructs whole-file reads and forbids echoing file contents into visible text. In live agent sessions this eliminated double-emitted file bodies and micro-read storms, and cut the reasoning share of generated tokens from 75 percent to under 30.
  • Sessions survive client history rewrites. When an agent client compacts or rewrites its transcript, the session used to rotate to a fresh anonymous identity and lose its warm cache, forcing cold full-context re-prefills mid-session. A common-prefix fallback now keeps the session identity when at least 4096 tokens and a quarter of the prompt still match.
  • An optional reasoning budget for agent turns: --agent-thinking-budget (env MTPLX_THINKING_BUDGET) force-closes a runaway thinking segment at a token budget so the turn proceeds to its answer or tool call. Off by default; nothing touches generation unless you opt in. Every engagement is surfaced in telemetry.
  • New diagnostics for agent workloads: --request-log-jsonl appends every request record as a durable JSON line, and scripts/session_forensics.py correlates that log with an OpenCode database into one timeline, with detectors for re-prefill rewinds, stalls, thinking marathons, double emissions, and session identity churn.

Hardening from the independent source review (#86, #103, #107, #182)

  • Context-copy drafting no longer accepts past a stop token. A full block accept could advance the cache and model state beyond the emitted response and poison the session cache for the next turn on recurrent models. Copy blocks are also strictly prompt-only now, so the lane never drafts from the model's own output.
  • Silent stream hangs are contained (#86). If a stream receives nothing while the engine heartbeat is frozen for five minutes, the request fails with a structured, diagnosable error instead of hanging forever. Healthy long prefills never trip it, and the daemon is never killed.
  • Long-running daemons no longer accumulate unbounded telemetry. The session-bank eviction log, scheduler counters, and per-session dashboards are all bounded now.
  • Vision works on a bare pip install mtplx (#103): Pillow moved from the server extra into the base dependencies to match the advertised image support.
  • The desktop app recognizes assistant-pair model repos the same way the Python runtime does (#107), so the official Gemma4 pair repos classify as ready to download instead of unreadable.
  • Models quantized at 5-bit and other widths that do not divide 32 now load correctly. The group-size inference used floor division per packed word and produced impossible group sizes. Diagnosed and fixed by @Jonathangadeaharder, validated down to the MLX packing layer.
  • mtplx doctor --json output is guaranteed machine-parseable even when third-party libraries print their own errors during import.

Contributor fixes (#178, #179, #180, #181)

  • Interrupted model downloads are detected as incomplete instead of being treated as ready. By @titan550.
  • Fan restore goes through the ThermalForge daemon socket instead of a CLI path that could kill the app, plus a fan-control code cleanup. By @titan550.

Under the hood

  • The runtime now stamps its real version everywhere, including forge provenance. A version-skew bug class where an older engine silently ignored newer recipe fields was diagnosed this cycle through exactly that stamp (#184), and the fix work is queued.
  • The agent wall-time lab used to validate this release ships in scripts/walltime-lab/: a harness that drives real OpenCode builds against a daemon and scores completion, useful for reproducing agent performance reports.
Get it. Download the current DMG, or brew upgrade mtplx / pip install -U mtplx. Every published speed number with its conditions is on the benchmarks page; the archive of every version is on the releases page.