A local AI coding assistant for macOS, powered by Apple MLX. By default nothing leaves your Mac: the model runs in-process on your GPU. When you want more, the same chat can use Ollama, TinyLLM, TinyChat, OpenWebUI, the Nova Gateway, OpenRouter or OpenAI through NovaAIKit, with Auto failover.
MLX Code runs language models directly on Apple Silicon using mlx-swift and mlx-swift-lm 3.x. It provides a chat interface with 14 built-in tools that can read files, search code, run shell commands, build Xcode projects, manage Git repos and work with GitHub.
Written by Jordan Koch.
- Architecture
- Features
- AI backends (NovaAIKit)
- Models
- Nova API Server
- Requirements
- Installation
- Building
- Security
- Test Suite
- Version History
graph TB
subgraph Views["SwiftUI Views"]
ChatView["ChatView<br/>+ NovaAIStatusIndicator"]
SettingsView["SettingsView<br/>AI tab = NovaAISettingsView"]
PromptTemplatesView
GitHubPanelView
CodeAnalysisPanelView
OnboardingView
end
subgraph ViewModels
ChatVM["ChatViewModel"]
ProjectVM["ProjectViewModel"]
GitHubVM["GitHubViewModel"]
CodeAnalysisVM["CodeAnalysisViewModel"]
end
subgraph Tools["Tool Execution Layer - 14 tools"]
direction LR
Core["Core: File Ops, Bash, Grep, Glob, Edit"]
Dev["Dev: Xcode, Git, GitHub, Code Nav,<br/>Code Analysis, Error Diagnosis,<br/>Test Gen, Diff Preview, Help"]
end
subgraph AI["AI layer"]
AppAI["AppAI.shared<br/>(NovaAI, appID mlxcode)"]
Kit["NovaAIKit 1.0.0<br/>Auto failover, retries, timeouts,<br/>validation + fallback, Keychain keys"]
Local["NovaAIClosureModel<br/>(mlx backend = in-process)"]
end
subgraph Engine["In-process inference"]
MLXService["MLXService (actor)"]
Generator["InferenceGenerator (protocol)"]
MLXLM["mlx-swift-lm 3.x ModelContainer"]
ContextManager["ContextManager (actor)"]
UserMemories["UserMemories (actor)"]
end
subgraph Remote["HTTP backends"]
Ollama
TinyLLM
TinyChat
OpenWebUI
Nova["Nova Gateway / Fleet"]
OpenRouter
OpenAI
end
subgraph Security
CommandValidator
ModelSecurityValidator["SafeTensors-only Validator"]
Keychain["macOS Keychain<br/>com.jkoch.mlxcode.*"]
SecureLogger
RepDetector["RepetitionDetector"]
end
subgraph External["Extensions"]
XcodeExt["Xcode Extension - 5 commands"]
Widget["Desktop Widget - 3 sizes"]
NovaAPI["Nova API - port 37422"]
end
Views --> ViewModels
ViewModels --> Tools
ChatVM -->|"backend = MLX"| MLXService
ChatVM -->|"Auto / other backends"| AppAI
AppAI --> Kit
Kit --> Local --> MLXService
Kit --> Remote
Kit --> Keychain
MLXService --> Generator --> MLXLM
ContextManager --> AppAI
Views -.-> External
NovaAPI --> ChatVM
flowchart TD
Send["User sends a message"] --> Ctx["ContextManager assembles<br/>system prompt + history + project"]
Ctx --> On{"Use AI on?"}
On -- no --> Notice["Transcript: 'AI is off' notice<br/>(nothing sent)"]
On -- yes --> Which{"Backend"}
Which -- "MLX (default)" --> Loaded{"Model loaded?"}
Loaded -- no --> Ask["Ask the user to load a model<br/>or pick another backend"]
Loaded -- yes --> InProc["MLXService.chatCompletion<br/>token streaming, native ToolSpecs, Stop"]
Which -- "Auto / Ollama / TinyLLM / TinyChat /<br/>OpenWebUI / Nova / OpenRouter / OpenAI" --> Stream["AppAI.shared.stream(request)<br/>failover, retries, timeouts"]
Stream -- ok --> Reply["Streamed reply"]
Stream -- "all down" --> Err["Transcript: '⚠️ No reply: reason'<br/>UI stays usable"]
InProc --> Reply
Reply --> ToolsQ{"Tool calls?"}
ToolsQ -- yes --> Run["Approve + run tools,<br/>continue the turn"]
ToolsQ -- no --> Done["Saved conversation"]
flowchart LR
subgraph App["MLX Code"]
VM["ChatViewModel<br/>generationTask"]
Settings["AppSettings<br/>KV cache: bits / max size / prefill step"]
Params["ModelParameters<br/>temp, topP, topK, minP,<br/>repetition penalty + context"]
Svc["MLXService (actor)<br/>busy flag, cancellation,<br/>tool-call mapping"]
Mapper["GenerationParameterMapper"]
Specs["ToolSpecBuilder<br/>ToolRegistry → [ToolSpec]"]
Fmt["NativeToolCallFormatter<br/>toolCall → <tool>{json}</tool>"]
end
subgraph LM["mlx-swift-lm 3.32"]
Loader["LLMModelFactory.loadContainer(from: URL,<br/>using: #huggingFaceTokenizerLoader())"]
Container["ModelContainer.prepare / generate"]
Stream["AsyncStream<Generation><br/>.chunk / .toolCall / .info"]
end
subgraph HF["Hugging Face"]
Hub["HubClient.downloadSnapshot<br/>*.safetensors *.json *.jinja *.txt *.model"]
Tok["swift-transformers Tokenizers<br/>(chat template)"]
end
Hub --> Loader
Tok --> Loader
VM --> Svc
Specs --> Svc
Params --> Mapper
Settings --> Mapper
Mapper -->|GenerateParameters| Container
Svc --> Container --> Stream
Stream -->|chunks| Svc
Stream -->|tool calls| Fmt --> Svc
Svc -->|stream to UI| VM
VM -. "Stop: Task.cancel()" .-> Svc
- Real Stop: Stop cancels the generation task; token generation ends inside mlx-swift-lm (or the HTTP stream is closed for a remote backend) and the next request starts immediately. The partial answer is kept.
- Native tool calling: the tool registry is sent to the model's chat template as
ToolSpecs, andGeneration.toolCallevents (Qwen, Llama, Mistral, Gemma, Harmony and more) feed the approval flow, several calls per turn. The<tool>…</tool>text protocol is the fallback, and it's also what remote backends use. - KV-cache controls: Settings › Model: KV quantization (none / 8-bit / 4-bit), max KV size (rotating cache), prefill step size.
- Full sampling controls: temperature, top-p, top-k, min-p, repetition penalty (default 1.1) and repetition context size.
- Complete downloads: chat templates (
*.jinja), BPE merges (*.txt) and SentencePiece models (*.model) come with weights and configs. - Context detection: the context window is read from
config.json, including nestedtext_config.
| Tool | Purpose | Tier |
|---|---|---|
| File Operations | Read, write, edit, list, delete files | Core |
| Bash | Run shell commands | Core |
| Grep | Search file contents with regex | Core |
| Glob | Find files by pattern | Core |
| Edit | Apply targeted file edits | Core |
| Xcode | Build, test, clean, archive, deploy | Dev |
| Git | Status, diff, commit, branch, log, push, pull | Dev |
| GitHub | Issues, PRs, branches, credential scanning | Dev |
| Code Navigation | Jump to definitions, find symbols | Dev |
| Code Analysis | Metrics, dependencies, lint | Dev |
| Error Diagnosis | Analyze and explain build errors | Dev |
| Test Generation | Create unit tests from source files | Dev |
| Diff Preview | Show before/after file changes | Dev |
| Help | List available commands and usage | Dev |
Read-only tools auto-approve. Write and execute tools need confirmation.
/commit /review /test /docs
/refactor /explain /optimize /fix
/search /plan /help /clear
/review, /test, /docs, /refactor, /explain, /optimize and /plan ask the selected AI backend (through
NovaAIKit). /search, /index, /help and /clear don't need a model.
15 curated prompt templates across 9 categories (Review, Debug, Generate, Refactor, Test, Document, Security, Performance, Deploy). Browse, fill variables, preview and send, all in-app.
Select code in Xcode and use Editor › MLX Code: Explain Selection, Refactor Selection, Generate Tests, Fix
Issues, Ask MLX Code. The extension hands the request to the app through the shared App Group container and the
mlxcode:// URL scheme; the app answers with whichever AI backend is selected.
Small, medium and large sizes showing model status, token speed, memory use and quick-action deep links.
View and create issues, list and create PRs, manage branches, scan for exposed credentials before pushing.
50+ built-in coding standards across 8 categories injected into the system prompt. Custom memories stay local.
- Token budgeting: 70% messages, 20% project context, 10% summary
- Automatic message compaction when the context fills up; message summaries come from the AI backend and fall back to the message's first sentence when AI is off or unavailable
- Live context-window usage bar
- Project context auto-included when a workspace is open
Swift, Python, JavaScript, TypeScript, Bash, JSON and Objective-C.
MLX Code uses NovaAIKit 1.0.0 (products NovaAIKit and NovaAIKitMLX), the
same AI layer as every other Jordan Koch macOS app. Settings › AI is NovaAIKit's settings view:
| Setting | What it does |
|---|---|
| Use AI | On/off. Off = nothing is sent to any model. |
| Backend | Auto, Ollama, MLX (Apple Silicon), TinyLLM, TinyChat, OpenWebUI, Nova Gateway / Fleet, OpenRouter, OpenAI |
| Model / URL | Per backend; models are listed from the backend, URLs are editable |
| API keys | SecureField → macOS Keychain only; a stored key is shown as "stored", never as text |
| Timeout | 10 s – 300 s per request |
| Test connection | Probes the selected backend (Auto: all of them) |
- MLX (Apple Silicon) is the default and means the model loaded in MLX Code itself: the app's mlx-swift-lm engine
is plugged into NovaAIKit's
mlxbackend (NovaAIClosureModel), so chat keeps native token streaming, tool specs and Stop. Nothing leaves the Mac. - Auto tries Ollama → MLX (in-process) → TinyChat → TinyLLM → OpenWebUI → Nova Gateway → OpenRouter → OpenAI, skipping servers that just failed and keyed clouds without a key. Transient errors are retried with backoff.
- Status indicator in the chat toolbar: green = reachable (backend, host, latency), orange = offline, grey = off. Click it to re-check.
| Feature | AI off | Every backend down / reply rejected |
|---|---|---|
| Chat | Transcript notice ("AI is off…"), nothing sent | Transcript shows " |
| Message summaries (context compaction) | First sentence of the message | First sentence of the message |
| AI slash commands, multi-file transform/generate | Readable "AI is turned off" error | The backend error |
Tools, Git/GitHub, Xcode, project panels, /search, /index |
Work normally | Work normally |
Summaries are validated (non-empty, plain text, ≤ 240 characters) before use.
- Keys live only in the Keychain under
com.jkoch.mlxcode.<backend>(accountapiKey), and are only sent over https. - On first launch of 6.5.0 the v6.4 balancer's OpenRouter key (
com.jordankoch.mlxcode.openrouter) is copied tocom.jkoch.mlxcode.openrouter, verified, and the old item is deleted. The balancer toggles become Auto if any was on, and a custom Nova Gateway URL is kept. Any plaintext AI keys in UserDefaults are moved to the Keychain or deleted. - Google/Azure/AWS/IBM backends are not supported (MLX Code never shipped them).
Uses mlx-community models from Hugging Face, quantized for Apple Silicon.
| Model | Size | Context | Best for |
|---|---|---|---|
| Qwen3.5 9B (default) | ~5 GB | 256K | General coding, native tool calling (16 GB+ RAM) |
| Qwen3-Coder 30B-A3B (MoE) | ~17 GB | 256K | Agentic coding (32 GB+ RAM) |
| Devstral Small 2 24B | ~14 GB | 384K | Software-engineering agent tasks (32 GB+ RAM) |
| Qwen3.6 35B-A3B (MoE) | ~20 GB | 256K | High quality at MoE speed (32 GB+ RAM) |
| Qwen3.6 27B | ~16 GB | 256K | Best quality, slower (32 GB+ RAM) |
| Gemma 4 E4B | ~4 GB | 128K | Efficient general model |
| Qwen3.5 2B | ~1.5 GB | 256K | Fast edits on 8 GB Macs |
| Qwen 2.5 7B (legacy) | ~4 GB | 32K | Kept for existing installs |
All entries are mlx-community 4-bit repos supported by mlx-swift-lm 3.32. Long contexts need a lot of memory; use
the KV-cache settings to cap or quantize them. Models download through the native Swift Hugging Face client
(HubClient). Custom mlx-community models work too. Only SafeTensors weights are accepted; PyTorch pickle
files are rejected.
Local HTTP API on port 37422 (loopback only). POST/DELETE need the app's bearer token. Chat requests go through the same routing as the UI (selected backend, AI on/off).
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/status | App status, model state, uptime |
| GET | /api/ping | Health check |
| GET | /api/conversations | List all conversations |
| POST | /api/chat | Send message, get response |
| GET | /api/model | Current model info |
| POST | /api/model/load | Load a model |
| GET | /api/metrics | Performance metrics (tokens/sec, memory) |
| POST | /api/cancel | Cancel current generation |
| GET | /api/prompts | List prompt templates |
| POST | /api/prompts/render | Render template with variables |
- macOS 14.0 (Sonoma) or later
- Apple Silicon (M1, M2, M3, M4)
- 8 GB RAM minimum (16 GB recommended) for in-process models
- No Python required: inference and downloads are pure Swift
- Optional: a local or LAN Ollama / TinyLLM / TinyChat / OpenWebUI server, the Nova Gateway, or an OpenRouter / OpenAI key
- Xcode 26+ only to build from source
- Get
MLXCode-v6.5.0-build10.dmg(Releases, or/Volumes/Data/xcode/binaries/<date>-MLXCode-v6.5.0/). - Open it and drag MLX Code into Applications.
- Launch it, download a model from Settings › Model (or pick another backend in Settings › AI).
"MLX Code can't be opened because the developer cannot be verified"? The build isn't Developer-ID-signed and notarized. Open it anyway: on macOS 15 and later, double-click it, dismiss the dialog, then System Settings › Privacy & Security › Open Anyway. Or:
xattr -dr com.apple.quarantine "/Applications/MLX Code.app". Notarized releases open with no prompt. Maintainers: see RELEASE.md.
- System Settings › Privacy & Security › Extensions › Xcode Source Editor
- Enable MLX Code
- Restart Xcode, select code, then Editor › MLX Code
Requires Xcode 26 or later and Apple's Metal Toolchain (MLX compiles Metal shaders):
xcodebuild -downloadComponent MetalToolchain
git clone git@github.com:kochj23/MLXCode.git
cd MLXCode
open "MLX Code.xcodeproj" # resolves mlx-swift, mlx-swift-lm, swift-huggingface, swift-transformers, NovaAIKitCommand-line builds must pass -skipMacroValidation -skipPackagePluginValidation (mlx-swift-lm's
#huggingFaceTokenizerLoader() is a Swift macro):
xcodebuild -project "MLX Code.xcodeproj" -scheme "MLX Code" -configuration Release \
-skipMacroValidation -skipPackagePluginValidation buildPinned dependency versions are committed in
MLX Code.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved. Release: scripts/release.sh
(Developer ID + notarization, see RELEASE.md).
A wall of
CompileMetalFile … missing Metal Toolchainerrors means the Metal Toolchain isn't installed.
- Local by default: the default backend is the in-process MLX model; nothing leaves the Mac unless you pick a remote backend
- Keys in the Keychain only (
com.jkoch.mlxcode.*), never in UserDefaults, files or the environment; refused over plain http; never logged - CommandValidator blocks dangerous shell patterns (rm -rf, fork bombs, sudo, eval, curl|sh)
- ModelSecurityValidator enforces SafeTensors-only and verifies hashes via CryptoKit
- Nova API binds to loopback, requires a bearer token for POST/DELETE, no wildcard CORS
- SecureLogger with category-based logging, no PII in logs
- RepetitionDetector stops inference loops
- No telemetry, analytics or crash reporting
- No web browsing or URL fetching
- No image/video/audio generation
- Small local models (3–14B) mean imperfect multi-step reasoning; tool calls from local models are sometimes malformed (JSON auto-repair helps)
- The Xcode extension opens the app rather than answering inline
575 tests across 28 files: models, services, security, context budgeting, build parsing, Keychain, tool registry,
JSON repair, prompt templates, source-level security scans, the inference engine (parameter mapping,
Stop/cancellation, native tool calls, download patterns, catalog, context parsing) via a mock generator, and the
NovaAIKit integration (AIKitAdoptionTests: unit, security, retry, performance, integration, functional and frame
tests with a stub URLProtocol and in-memory Keychain; no network, no real keys, no model download).
xcodebuild -project "MLX Code.xcodeproj" -scheme "MLX Code" \
-destination "platform=macOS" -skipMacroValidation -skipPackagePluginValidation testOptional real-model integration test (skipped by default):
TEST_RUNNER_MLXCODE_INTEGRATION=1 TEST_RUNNER_MLXCODE_MODEL_PATH=/path/to/mlx-model \
xcodebuild -project "MLX Code.xcodeproj" -scheme "MLX Code" -destination "platform=macOS" \
-skipMacroValidation -only-testing:"MLX Code Tests/MLXServiceTests/testIntegrationLoadAndStreamRealModel" test| Version | Date | Highlights |
|---|---|---|
| 6.5.0 | 2026-11-11 | AI moved to NovaAIKit 1.0.0: AI on/off, 9-way backend picker with Auto failover, in-process MLX as the mlx backend, Keychain-only keys (com.jkoch.mlxcode.*), status indicator, validated summaries with built-in fallback; the v6.4 balancer, OpenRouter client and KeychainStore removed |
| 6.4.0 | 2026-11-06 | mlx-swift-lm 3.32 / mlx-swift 0.32.3, real Stop, native tool calling, KV-cache settings, min-p, model catalog refresh, complete tokenizer downloads; multi-model load balancer |
| 6.3.0 | 2026-03-04 | Native model downloads, tool-call reliability, Xcode Source Editor Extension |
| 6.1.0 | 2026-02-26 | Security hardening and code-quality audit |
| 5.0.0 | 2026-02-19 | Focused rewrite: removed media generation, RAG and auxiliary panels |
| 4.0.0 | 2026-02-19 | Native mlx-swift inference |
| 1.0.0 | 2025-11-18 | Initial release |
See CHANGELOG.md for details.
MIT License. Copyright 2025-2026 Jordan Koch. See LICENSE.
Written by Jordan Koch (@kochj23)