The model layer
New in 1.0 — offer more than one model, without creating your own provider abstraction
The 0.8 SDK gives your native macOS app a real MCP client, the
Calendar / Reminders / Contacts / Location connectors, ready-made Tools, and
Keychain-backed OAuth — all still here, all unchanged. 1.0 adds the piece you reach for
when which model becomes a real question in your app.
And the MCP client is no longer Apple-only. The same tool-calling loop runs on top of whichever provider you route to, so a session backed by Claude or a downloaded open-weight model discovers and calls your MCP servers — Slack, GitHub, Notion, Linear, and the rest — exactly the way an Apple on-device session does.
Start from working code
Every API on this page is exercised by a small, runnable example you can clone and build — use the closest one as a starting reference rather than writing from scratch. For the model layer, three of them, smallest to fullest:
| Example | Start here if you want… |
|---|---|
repo-qa-local |
the smallest "download and run an open-weight model" — one file, a CLI that answers questions about a GitHub repo through an MCP server. About 20 lines different from the Apple-model version next to it. |
code-buddy |
the full pattern: a CLI coding agent with .heavy /
.light routes to two local models (one kept warm), Core's Workspace
tools, an MCP docs server, streamed output, and two host-owned Process
tools the SDK deliberately doesn't ship. Runs one task and stops, or drops into an
interactive >> loop over one persistent session (Ctrl-C cancels the
current turn; a second press quits). Every API below has a
"→ code-buddy uses this" pointer in the guide. |
workspace-buddy-local |
a sandboxed GUI app running a downloaded model — the Mac App Store shape. Folder picker + security-scoped bookmark, the model edits files, and the model download works inside the App Sandbox container. |
Each folder has a README with copy-paste setup and a real "verified live" transcript.
All examples →
— the MCP-client and connector ones (repo-qa, plate-today,
components-demo) need no model download.
annotated-examples.md
walks every one line by line.
Four providers, one protocol
| Provider | Backs | Ships in | macOS |
|---|---|---|---|
SystemModelProvider | Apple's on-device model | Core | 26+ |
PCCModelProvider | Apple Private Cloud Compute — not functional in this beta, see beta notes | Core | 27 |
ClaudeModelProvider | Claude (your API key or auth) | LocalLMLabSDKClaude | 27 |
MLXModelProvider | Open-weight models you download and run locally | Inference | 27 |
On macOS 26 the 27-only providers are simply not registered;
lab.models.availability(for:) reports their schemes as
.requiresOS("macOS 27") and ModelPickerView shows them as
disabled rows.
Register the providers you want on a LocalLMLab instance (the optional front
door that also bundles the MCP manager and the connector facades), name models with
routes, and make a session. Adoption is small — this is
repo-qa-local's
entire change over the Apple-model repo-qa next to it; the rest of
the two files is identical:
let mlx = MLXModelProvider(residentModelLimit: 1)
let lab = LocalLMLab(configuration: .init(providers: [mlx, SystemModelProvider()]))
let modelID = ModelID(scheme: "mlx", rest: "mlx-community/Qwen3-8B-4bit")!
lab.models.route(.local, to: modelID)
if case .notDownloaded = lab.models.availability(for: modelID) {
_ = try await mlx.validate(modelID.rest) // fits this Mac's RAM? MLX format?
for try await event in mlx.download(modelID.rest) { … } // stream progress %
}
let session = try lab.makeSession(route: .local, tools: tools, instructions: instructions)
for try await partial in session.languageModelSession.streamResponse(to: task) { … }
makeSession merges your tools with the enabled MCP tools whatever the
route — your own Tools, the connectors, and the MCP client attach to
Apple on-device, Claude, and MLX sessions alike; .events
is a side-channel for tool-call and context-compaction progress; contextBudget
and retryOnContextOverflow carry a long session.
The memory story
MLXModelProvider isn't "load and go" on a constrained Mac:
validate preflights a model (MLX format? architecture supported? weights vs.
this Mac's RAM?) with no download; download streams progress;
capabilityProbe is the authoritative check of whether a downloaded model can
actually tool-call. residentModelLimit caps how many models stay in RAM —
switching routes evicts the other — and residencyEventStream reports it.
For a starting shortlist of which open-weight models tool-call and which don't, see
tested-models.md.
Three binaries
LocalLMLabSDKCore is the engine — link it always (macOS 26 floor).
LocalLMLabSDKInference is the MLX runtime (~49 MB, the whole stack
statically linked) — link it only if you use MLXModelProvider.
LocalLMLabSDKClaude holds ClaudeModelProvider — link it
only if you offer Claude, and note that its dependency is macOS-27-pinned, so it
forces a macOS 27 deployment target on whatever links it. All three are
binaryTargets on the same GitHub release, all Developer-ID signed and
notarized. See
code-buddy's Package.swift
for the multi-binary manifest shape.
Migrating from 0.8.x
- Platform: keep
platforms: [.macOS("26.0")]; build with the Xcode 27 beta. - One source-break:
MCPConnectionStatusandMCPServerErrorare non-frozen now — an exhaustiveswitchneeds@unknown default. That's the only break if you use just the MCP client. Nothing was renamed or removed. (CustomModelProviderconformances also change:languageModel(for:)→makeSession(for:…).) - Point
knownSDKReleasesat1.0.0-beta.2with the checksum from the release's.sha256asset.
Docs
- sdk-guide.md — the full guide; §6a is the model layer.
- migrating-to-1.0.md — 0.8.x → 1.0.
- tested-models.md — which open-weight models tool-call.
- api-surface.md — the machine-generated public API list.
- annotated-examples.md — every reference app's source, SDK touchpoints marked inline.
- CHANGELOG.md
Contact
Building something on this, or hit a rough edge? neuron@thisbrain.ai or the Discord.