Skip to content

feat(local-model): "Use the local model" works on a buyer's Mac - #347

Merged
AVADSA25 merged 5 commits into
mainfrom
feat/local-model-for-buyers
Sep 4, 2026
Merged

feat(local-model): "Use the local model" works on a buyer's Mac#347
AVADSA25 merged 5 commits into
mainfrom
feat/local-model-for-buyers

Conversation

@AVADSA25

@AVADSA25 AVADSA25 commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Why

The connect screen offered a local model that could not exist on a fresh Mac. Three independent reasons, each fatal:

  1. The bundled Python had no mlx_vlm — nothing to serve a model with.
  2. scripts/start_model_server.sh wasn't in the bundle, and hardcoded ~/codec-qwen38-venv, which only the developer's machine has.
  3. first_run.py ran fetch_models without --yes — a dry run. The "bundled" 4.3 GB LLM was never downloaded.

Operator decision (2026-09-04): accept the bundle growing for the best first-run experience. It landed at 900 MB notarized, vs the 1.2 GB estimate.

What changed

  • requirements.txt: mlx-vlm gated to darwin/arm64 so Linux CI never tries to resolve it; jinja2 unconditionally (see below).
  • start_model_server.sh resolves, in order: CODEC_MODEL_VENV → the app's own Resources/python → the dev venv → system. Sets PYTHONDONTWRITEBYTECODE so it can't corrupt the signed bundle.
  • build_app.sh ships the script into Resources/app/scripts/, where services.json already expects it.
  • The connect screen gets a Download the local model button with live GB/percent progress (/api/setup/download_local, /api/setup/download_status). Refuses a duplicate, refuses re-downloading present weights, reports failure honestly — including a fetch that "succeeds" without writing weights.
  • A downloaded model is discovered and selectable immediately.

What the gates caught

jinja2 was missing. The bundled server started, listened, and then failed every completion: apply_chat_template requires jinja2. transformers does not declare it. Only the end-to-end gate — which asks the bundled interpreter for a real completion on a port PM2 doesn't own — found it. Confirmed by installing jinja2 into the built bundle and getting READY back, then rebuilt properly.

A download-ordering bug. start_download checked for existing weights before checking for a running download, so a half-finished download (≥500 MB on disk) would have been refused as "already downloaded". The G4 test's realistic fixture caught it.

Three of my own gates were wrong and were corrected rather than the code: G1 required mlx_lm (not on the serving path); G2's probe copy ran from /tmp and blamed the script for its own relocation; G2 then failed the correct answer on an un-normalised path string.

A pre-existing test bug: test_a12_invariant scanned dist/ and reported the bundled copies of the repo plus every third-party server as offenders the moment anyone builds locally. Walker now skips build output.

Evidence

G3: bundled server answered: READY — from Contents/Resources/python/bin/python3, asserted by process path. Seven gates; G7 (DMG notarized + bundles the MLX-capable app) is added on the follow-up commit once the DMG returns from Apple.

🤖 Generated with Claude Code

Mikarina13 and others added 5 commits September 4, 2026 16:08
… the connect screen

Checkpoint before the build verifies it; final message on the squash.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Scanning built output reports the repo's own bundled copies and every
third-party server (mlx_vlm, transformers) as offenders the moment someone
builds the app locally and runs pytest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…2 gates corrected

The bundled server listened and then failed every completion:
'apply_chat_template requires jinja2'. transformers does not declare it.
Found by the e2e gate; confirmed by installing jinja2 into the built bundle
and getting READY back. Declared unconditionally (pure Python).

G1 dropped mlx_lm (not on the serving path) and now requires jinja2. G2's
probe copy ran from /tmp, so the script resolved no bundled interpreter and
the gate blamed the script for its own mistake; the probe now runs beside
the script.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-independent token

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@AVADSA25
AVADSA25 merged commit 0752cb9 into main Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants