Introducing FoundationModelsKit: Production Patterns for Foundation Models on iOS
At WWDC26, Apple made a bold move: Foundation Models now ship with iOS. The model arrives on every device, ready to use, with no download or external dependency. Capability in the OS itself.
But capability without patterns is just a door. Walking through it safely, at scale, is something else entirely.
That’s what FoundationModelsKit is for.
The Problem WWDC26 Exposed
When I analyzed the Foundation Models and Privacy & Security labs at WWDC26, three decisions emerged as critical:
Which model do I call? On-device is fast and private but limited (4K token window). Private Cloud Compute is smarter (32K window) but slower. A third-party API is capable but carries data disclosure risk. You need a strategy, not a guess.
What happens when the context window fills? The 4K window compacts fast in real conversations. You could naively drop old messages, but that loses context. Or you could summarize — but that’s another model call. You need a pattern.
How do I know the output is good? Models are non-deterministic. The same input may produce inconsistent outputs. Shipping without evaluation is shipping blind. You need measurement.
Most engineers solving this are building it from scratch, in every app. There’s no standard library. So I built one.
What Is FoundationModelsKit?
A production-ready Swift library that abstracts Foundation Models behind a protocol and gives you:
Multi-tier routing: Decide on-device vs. PCC vs. third-party based on request complexity, not guesswork
Context management: Automatic transcript compaction with smart summarization
Evaluation framework: Pluggable quality metrics to gate model outputs
Region awareness: Know which models are available where
One clean facade: Chain all of this with a single
sendMessage()call
It’s protocol-first, so you can swap implementations (real models, mocks, custom) without rewriting your app. It’s all Sendable and Codable, so it threads safely and serializes cleanly. And it has zero external dependencies — pure Swift, iOS 18+ / macOS 15+.
The Architecture (Eight Phases)
Phase 1: LanguageModelProviding (The Protocol)
Every model in the world becomes a single LanguageModelProviding:
protocol LanguageModelProviding: Sendable {
func sendMessage(request: ModelRequest) async throws -> ModelResponse
}
Concrete implementations:
MockLanguageModel— for testingReal Foundation Models adapter — coming in Phase 9
A third-party API wrapper — up to you
This abstraction is the entire foundation. Everything else plugs into it.
Phase 2: ModelRouter (Smart Routing)
ModelRouter decides which model to use. Default heuristic:
Small request (< 500 chars), no tools, simple task → on-device (fast, private)
Larger or complex → PCC (smarter, more context)
PCC unavailable → third-party API (fallback)
You control the tiers you provide. If you don’t have PCC, the router skips it.
let router = ModelRouter(
onDevice: onDeviceModel,
pcc: pccModel,
thirdParty: anthropicModel
)
let response = try await router.routeRequest(request)
// Automatically picked the right tier.
Phase 3: ConversationStore (Context Management)
Conversations grow fast. The 4K on-device window fills in ~10–15 turns. ConversationStore manages the transcript and auto-compacts when needed.
let store = ConversationStore()
store.addEntry(.user, "Summarize Foundation Models")
store.addEntry(.assistant, "Foundation Models are…")
// Later, when near the limit:
if store.shouldCompact(maxTokens: 4096) {
try await store.compact(using: router, maxTokens: 4096)
}
// Old entries → summarized into one. Recent entries stay intact.
Phase 4: DynamicProfileBuilder (Configuration)
Define routing and privacy policies as profiles:
let profile = DynamicProfile.balanced
.withRoutingStrategy(.preferOnDevice)
.withMaxContextTokens(4096)
.withPrivacySensitivity(.high)
.withAutoCompact(true)
Pre-built profiles: onDeviceOnly, balanced, cloudFirst. Or build your own.
Phase 5: EvaluationSuite (Quality Gates)
Evaluate outputs before shipping them. Pluggable metrics:
let metrics: [EvaluationMetric] = [
LengthMetric(min: 50, max: 500),
ContainsKeywordsMetric(["Foundation", "Model"]),
NonEmptyMetric()
]
let suite = EvaluationSuite(metrics: metrics)
let result = try await suite.evaluate(response: modelResponse)
if result.overallPassed {
// Safe to use
} else {
// Log the failure, retry or fallback
print(result.scores) // Individual metric details
}
Phase 6: RegionalAvailability (Geography-Aware)
Not all models are available everywhere. Track it:
let regional = RegionalAvailability(availabilities: [
ModelAvailability(region: .usEast, onDeviceAvailable: true, pccAvailable: true),
ModelAvailability(region: .eu, onDeviceAvailable: true, pccAvailable: false),
])
let tier = regional.bestTierFor(region: .eu)
// Returns .onDevice (PCC not available there)
Phase 7–8: SDKIntegration Facade
One entry point that chains everything:
let integration = SDKIntegration(
config: config,
router: router,
store: store,
evaluation: suite,
regional: regional
)
let (response, evaluation) = try await integration.sendMessage(request)
// Routed, stored in conversation, evaluated, logged. All in one call.
Why This Matters
Data residency: On-device keeps user data local. But it’s limited. You need smart routing to use it when safe and escalate when necessary.
Cost & latency: PCC is slower than on-device but cheaper than third-party. A router that respects complexity saves money and time.
Quality assurance: Models hallucinate. Evaluation gates prevent shipping nonsense to users.
Iteration: Without clean abstractions, swapping models or adding evaluation requires rewiring your entire app. FoundationModelsKit lets you iterate fast.
Testing: MockLanguageModel makes unit tests trivial. No network, no real API calls, no flaky tests.
What Comes Next (Phase 9)
When the iOS 27 SDK lands and I have the hardware, Phase 9 integrates the real Foundation Models API. The pattern stays the same; the implementation changes.
Until then, you have a complete pattern library and a mock implementation to learn from.
Get Started
# Add to Package.swift
.package(url: "https://github.com/divyaravitech/FoundationModelsKit.git", branch: "main"),
// In your target:
.product(name: "FoundationModelsKit", package: "FoundationModelsKit"),
Then:
import FoundationModelsKit
let mock = MockLanguageModel()
let router = ModelRouter(onDevice: mock)
let store = ConversationStore()
let suite = EvaluationSuite(metrics: [NonEmptyMetric()])
let config = FoundationModelsKitConfiguration(profile: .balanced)
let integration = SDKIntegration(
config: config,
router: router,
store: store,
evaluation: suite,
regional: RegionalAvailability(availabilities: [])
)
let request = ModelRequest(
content: "What are Foundation Models?",
taskComplexity: .medium,
privacySensitivity: .high
)
let (response, evaluation) = try await integration.sendMessage(request)
print(response.content)
Design Principles
Protocol-first: Swap implementations without changing code.
Sendable & Codable: Thread-safe, serializable, testable.
No magic: Every decision is explicit and tunable. Routing heuristics, context limits, evaluation thresholds — all configurable.
Production-ready: Tested, well-documented, ready to ship.
Why I Built This
At WWDC26, I realized the iOS community is about to ship a lot of Foundation Models features. Some will be brilliant. Many will be half-baked because there’s no playbook yet.
This is the playbook.
It’s not dogmatic. You don’t have to use all eight phases. Start with routing + context management (phases 1–3), add evaluation when you need it (phase 6), integrate region awareness when you go global (phase 7).
But the patterns are battle-tested against real WWDC26 scenarios. The architecture is clean. The code is simple. And it’s on GitHub, ready to use, ready to contribute to, ready to learn from.
Links
GitHub: https://github.com/divyaravitech/FoundationModelsKit
My WWDC26 analysis:
Questions? Comments? Open an issue on GitHub, or reach out on LinkedIn. I’m actively maintaining this and excited about where the community takes it.
Let’s build Foundation Models right.



This is awesome, well done can we use PCC now or we need to wait for september.