You are probably here because someone asked you to put a local model into an Apple app. This file is the contract: what this package is, what it will and will not do, and the mistakes that make an agent’s Core AI code fail on a real device. Read it before writing Swift, whether you are working inside this repo or adding it to someone else’s app.
Core AI is Apple’s on-device inference runtime (iOS/macOS 27, .aimodel bundles).
CoreAIKit is a community Swift package on top of it — model download and cache, a pinned
catalog of converted models, and task-level APIs. It is not affiliated with Apple.
CoreAIKit when the app needs a specific model, a capability the system does not have (VLM, OCR, TTS cloning, diarization, detection, depth, embeddings, forecasting), or behaviour that must not change under the user when the OS updates.
Check the SDK, not this list. ASR was on it until the iOS 27 Speech.framework
interface was read on 2026-08-05: SpeechAnalyzer + SpeechTranscriber do streaming and
word timestamps, which the kit’s own notes had been claiming as reasons to prefer a 3.2 GB
model. CoreAI.transcribe now defaults to Apple’s. A list of capabilities goes stale every
OS release; the SDK does not.
KitLanguageModel puts a catalog model behind Apple’s
own LanguageModelSession, so tool calling and @Generable guided generation work over a
third-party model.“Apple ships it” is not automatically “stop” — “this becomes a wrapper” is. The two are different tests and only the second one decides. Apple’s transcriber is as good, so the kit routes to it and keeps the 238 MB of diarization Apple cannot do — the feature survives the routing and got 14× cheaper. Apple’s voices are free and cannot be cloned, so the TTS models stay: routing those away would leave a wrapper around a public API with no reason to exist. Ask what is left after you route to Apple. If the answer is nothing, there was nothing to build.
Task ops — the result in one line, model resolved and cached behind the call:
import CoreAIOps
let text = try await CoreAI.transcribe(voiceMemoURL) // Whisper v3 turbo
let tldr = try await CoreAI.summarize(text)
let pii = try await CoreAI.redact(text) // GLiNER2
Model level — pick the model, stream, attach tools:
import CoreAIKit
let chat = try await ChatSession(catalog: "qwen3.5-2b")
for try await event in await chat.streamResponse(to: "Hello!") {
if case .response(let delta) = event { print(delta, terminator: "") }
}
Importing CoreAIOps re-exports the model layer, so one import covers both.
No Swift at all — a System One client that only needs an endpoint on this machine:
brew install john-rocky/tap/systemone && systemone serve (the /v1/systemone forms over a
catalog model; brew services start systemone keeps it running).
catalog.json holds 80 entries, each {id, kind, name, repo, revision, variants}. Ids look
like qwen3-0.6b, qwen3.5-2b, youtu-llm-2b, lfm2.5-1.2b — lowercase, hyphenated.
catalog.json, or call ModelCatalog at runtime. A
hallucinated id is a runtime failure the user sees, and model naming here does not follow
Hugging Face naming.license (the
SPDX id, CC-BY-NC-4.0 for a non-commercial model); the others are permissive, and every
model’s exact terms are on its zoo card. Do not ship a non-commercial model in a commercial
app.main to pick up a newer model — the pin is what was gated. Bumping one is a deliberate,
reviewed change (scripts/pin-catalog.py --check is what CI enforces).calibration in its entry: the temperature the
kit reads its answer-slot logits at by default, fitted by the maintainer (decide-cli
calibrate), with what it was fitted and reported on beside it. Only a model without a
temperature of its own gets one — never add it to a model whose author fitted or folded one in
(decider-0.8b’s card, a bundle’s decision block, qwen3.5-2b-decision). Like a pin, a
record in the live catalog reaches shipped apps without an update, so changing one is a
reviewed change.catalog.json has two generated copies — the built-in offline fallback and llms.txt —
and CI compares both byte for byte. Editing the catalog means re-running
scripts/gen-builtin-pins.py and scripts/gen_llms_txt.py in the same commit. Run
scripts/install-hooks.sh once per clone and a pre-commit hook checks this for you.sizeMB is decimal megabytes (bytes / 1,000,000) of everything a first run downloads,
the subtrees a loader fetches beside the variant path included. Never type it.
scripts/measure-catalog-sizes.py --write measures it at the pinned revision and writes
catalog.json and the built-in literal together; CI’s --check fails on a figure more than 2%
off. A multi-bundle entry also needs its loader’s subtrees in that script’s tables.ios variant in a repo with no ios/ lists the architectures it has an ios-<arch>/
for in architectures (["h19p"] while only ios-h19p/ is uploaded), and the list grows
with each folder uploaded. It is what an iPhone reads to show “not on this device” before a
download (CatalogEntry.unavailableReason); a variant without it is offered to every iPhone.
scripts/measure-catalog-sizes.py fails when the list and the repo’s folders at the pin differ.Most Core AI code an agent writes compiles and then fails in one of these ways:
os_proc_available_memory(), request com.apple.developer.kernel.increased-memory-limit,
and treat a 4B-class model on a phone as tight rather than routine.kind in the catalog.
A chat model is not a VLM; an ASR model is not a diarizer.Do not describe these models as “verified” without saying what was verified:
cos ≥ 0.999
otherwise), then re-gated after compression, then run on hardware.models/<model>/recipe.toml), the export script, and the verification script
are all published — python3 conversion/zoo_verify.py <hf-repo> re-checks a published bundle’s
tokenizer, chat template, context length and declared precision against its source model
without a GPU or a device.If a user is deciding whether to ship on this, tell them that accurately rather than either overselling it or waving them off.
Ask the human:
coreai-models fork pin in Package.swift. When it is bumped, the hermetic
tests cannot see an engine behavior change; before it lands, run two ChatSession turns on
a hybrid bundle (Qwen3.5 / LFM2.5 / Granite 4) on a Mac — the second turn is where a hybrid
engine change shows (0.2.3-zoo’s partial-reset refusal was found that way, not by CI).apple/*.If you were handed this repository to build the designed-but-unbuilt work, start at
docs/HANDOFF.md — it indexes every design document, gives the build order,
and lists the preconditions that must be resolved on hardware before the code they gate is
written.
docs/COOKBOOK.md — every “I want to …” mapped to its snippet.Examples/ — one buildable app per capability; start from the closest one.AGENTS.md for porting a
new model rather than consuming one.