Skip to content

M0 Stage 2: Native dependencies#6

Merged
tlkahn merged 2 commits into
mainfrom
worktree-m0-stage-2-native-dependencies
Jul 5, 2026
Merged

M0 Stage 2: Native dependencies#6
tlkahn merged 2 commits into
mainfrom
worktree-m0-stage-2-native-dependencies

Conversation

@tlkahn

@tlkahn tlkahn commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Stage 2 of the M0 skeleton (epic #1): attaches the two native dependencies to their owning modules so consumers inherit transitively. No other module touches native deps directly.

Phase 2.1: Vendored jinja_cpp

  • vendor/jinja_cpp/: llama.cpp's in-tree Jinja engine (common/jinja/, from implement new jinja template engine ggml-org/llama.cpp#18462, the PR that replaced minja) plus mlx-serve's 3-function C wrapper. Sources only - no libjinja.a, no obj/.
  • Ships llama.cpp's MIT LICENSE (the Apache-2.0 "Wang Zhaode" file in mlx-serve was a mislabel); provenance, renames, and vendoring recipe recorded in vendor/jinja_cpp/README.md.
  • Built from source in build.zig (-std=c++17 -O2 -DNDEBUG, Zig's bundled clang++/libc++) and attached to the chat module only; http/cli inherit the link transitively.
  • Smoke tests in src/chat/root.zig: jinja_last_error() null check, plus an end-to-end render of a trivial template proving the whole engine compiles and links.

Phase 2.2: Homebrew mlx gates

  • verifyBrewDeps (ported from mlx-serve): requires mlx >= 0.31.2 and mlx-c >= 0.6.0, strips _rev suffixes, fails configuration with an actionable brew install / brew upgrade message.
  • mlxbridge links mlxc + Metal/Foundation/IOKit/CoreFoundation, brew prefix resolved dynamically via brew --prefix; model/engine/http/cli inherit transitively.
  • Gated smoke test in src/mlxbridge/root.zig: mlx_version behind -Dgpu-tests; symbol resolution is proven at link time even when the test is skipped.

Verification

  • Failure path first (machine had neither dep): zig build fails with [mlxd] missing Homebrew dependency 'mlx' (>= 0.31.2). Install with: brew install mlx-c
  • After brew install mlx-c (mlx 0.31.2, mlx-c 0.6.0_2 - exercises the _rev strip): zig build succeeds
  • zig build test: 9 pass, 1 skip (the gated mlx test)
  • zig build test -Dgpu-tests: all 10 pass, mlx_version returns 0 with a non-empty version
  • No prebuilt artifacts in vendor/ (find vendor -name "*.a" -o -name "*.o" empty)
  • Dependency direction still enforced: temporary @import("chat") in engine root fails to compile (reverted)

Closes #3

🤖 Generated with Claude Code

https://claude.ai/code/session_017z9pjMWW4Jdhd244PSHHVV

tlkahn and others added 2 commits July 6, 2026 00:10
Stage 2 of the M0 skeleton: attach the two native dependencies to
their owning modules so consumers inherit transitively.

- vendor/jinja_cpp: llama.cpp's in-tree Jinja engine (MIT, from
  ggml-org/llama.cpp#18462) plus mlx-serve's C wrapper, sources only,
  built from source with -std=c++17 and attached to the chat module.
  Provenance and vendoring recipe recorded in README.md.
- Homebrew version gate on macOS: mlx >= 0.31.2, mlx-c >= 0.6.0,
  actionable install/upgrade message and exit(1) when unmet.
- mlxbridge links mlxc + Metal/Foundation/IOKit/CoreFoundation with
  brew prefix resolved dynamically.
- Smoke tests: chat renders a trivial jinja template end-to-end;
  mlxbridge calls mlx_version behind -Dgpu-tests (symbol resolution
  is proven at link time even when skipped).

Closes #3

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017z9pjMWW4Jdhd244PSHHVV
… policy

Addresses Stage 2 review findings 1-6:

- #1: C-owned render output is copied and freed inside renderTemplate;
  the malloc'd pointer never escapes src/chat/jinja.zig
- #2: jinja_last_error() is read immediately on the failing call, same
  thread; externs are non-pub so the pair cannot be split
- #3: mlxbridge file-header comment + CLAUDE.md convention establishing
  the ABI-drift policy (unchecked extern signatures, gated smoke tests,
  -Dgpu-tests run required on mlx-c upgrades)
- #4: error-path tests for invalid templates, with and without a
  Diagnostics out-param
- #5: inputs are dupeZ'd for NUL termination and interior NULs are
  rejected with error.InteriorNul before any C call
- #6: null-with-no-error (malloc failure in the wrapper) falls back to
  "unknown jinja render error" instead of reading a null message

Error messages surface via a std.json-style Diagnostics out-param.
Wrapper takes raw JSON slices for now; typed DTOs deferred to M1.
Unlike the old mlx-serve chat.zig, render failure returns an error
instead of silently falling back to a generic template.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017z9pjMWW4Jdhd244PSHHVV
@tlkahn tlkahn merged commit 77b43bb into main Jul 5, 2026
1 check passed
@tlkahn tlkahn deleted the worktree-m0-stage-2-native-dependencies branch July 5, 2026 17:40
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.

M0 Stage 2: Native dependencies

1 participant