{"id":49751958,"url":"https://github.com/roryford/manifoldkit","last_synced_at":"2026-06-12T00:00:22.664Z","repository":{"id":350205217,"uuid":"1194479871","full_name":"roryford/ManifoldKit","owner":"roryford","description":"Reusable SwiftUI chat framework for local and cloud LLM inference on Apple platforms","archived":false,"fork":false,"pushed_at":"2026-06-06T05:57:09.000Z","size":9609,"stargazers_count":1,"open_issues_count":19,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-06T07:16:45.625Z","etag":null,"topics":["anthropic","chat-ui","foundation-models","ios","llama-cpp","llm","macos","mcp","mlx","ollama","on-device-ai","openai","rag","swift","swiftui"],"latest_commit_sha":null,"homepage":"https://github.com/roryford/ManifoldKit#readme","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/roryford.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-28T12:16:43.000Z","updated_at":"2026-06-06T05:57:02.000Z","dependencies_parsed_at":"2026-05-15T05:01:19.703Z","dependency_job_id":null,"html_url":"https://github.com/roryford/ManifoldKit","commit_stats":null,"previous_names":["roryford/basechatkit","roryford/manifoldkit"],"tags_count":134,"template":false,"template_full_name":null,"purl":"pkg:github/roryford/ManifoldKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roryford%2FManifoldKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roryford%2FManifoldKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roryford%2FManifoldKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roryford%2FManifoldKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/roryford","download_url":"https://codeload.github.com/roryford/ManifoldKit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roryford%2FManifoldKit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34222709,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anthropic","chat-ui","foundation-models","ios","llama-cpp","llm","macos","mcp","mlx","ollama","on-device-ai","openai","rag","swift","swiftui"],"created_at":"2026-05-10T12:00:45.076Z","updated_at":"2026-06-12T00:00:22.272Z","avatar_url":"https://github.com/roryford.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ManifoldKit\n\n[![CI](https://github.com/roryford/ManifoldKit/actions/workflows/ci.yml/badge.svg)](https://github.com/roryford/ManifoldKit/actions/workflows/ci.yml)\n[![Release](https://img.shields.io/github/v/release/roryford/ManifoldKit?sort=semver)](https://github.com/roryford/ManifoldKit/releases/latest)\n[![License: MIT](https://img.shields.io/github/license/roryford/ManifoldKit)](LICENSE)\n[![Swift 6.1+](https://img.shields.io/badge/Swift-6.1%2B-F05138?logo=swift\u0026logoColor=white)](https://swift.org)\n[![Platforms](https://img.shields.io/badge/Platforms-iOS%2018%2B%20%7C%20macOS%2015%2B-blue)](#requirements)\n[![SwiftPM](https://img.shields.io/badge/SwiftPM-compatible-brightgreen?logo=swift\u0026logoColor=white)](#install)\n[![Documentation](https://img.shields.io/badge/Docs-DocC-blue?logo=swift\u0026logoColor=white)](https://roryford.github.io/ManifoldKit/documentation/manifoldkit/)\n\nThe only open-source Swift package that bundles UI, turn-loop runtime, persistence, and multi-backend inference into one drop-in chat product for Apple platforms.\n\n![ManifoldKit assembled full-stack hero — one import gives you the SwiftUI ChatView, the ConversationRuntime turn loop, SwiftData persistence, model-management UI, and every backend; competitors own a single band](docs/images/product/layer-cake-hero.svg)\n\n**New here?** Start with **[Why ManifoldKit — and how it's built to last](docs/WHY-MANIFOLDKIT.md)** for the honest \"what it solves and why trust it\" narrative, or jump to the [docs index](docs/README.md) for the full guided path from install to first token. Prefer rendered API reference? The full **[DocC documentation site](https://roryford.github.io/ManifoldKit/documentation/manifoldkit/)** ties every module's reference together under one navigable root.\n\nManifoldKit is a full-stack, multi-backend AI chat framework for iOS 18+ / macOS 15+. Import one umbrella package and you get a SwiftUI `ChatView`, the `ConversationRuntime` turn loop (send / regenerate / edit / cancel / branch), SwiftData persistence, model download and management UI, and inference backends spanning on-device (MLX, llama.cpp, Apple Foundation Models) and cloud (OpenAI, Anthropic, Ollama, LAN) — all behind one `InferenceBackend` protocol. Competitors ship a single layer; ManifoldKit ships the assembled product and the wiring between layers. It survives real failures — streaming retries, latest-wins model handoff, memory admission, certificate pinning, and a mock backend for app-level testing. See [docs/RELIABILITY.md](docs/RELIABILITY.md) for the source-backed contract, or [docs/POSITIONING.md](docs/POSITIONING.md) for the full \"why ManifoldKit vs. the field\" rationale.\n\n## Hello World\n\nAdd the package, then drop this into your app entry point. `ManifoldKit.quickStart()` builds the SwiftData container, registers the compiled-in backends, and wires up a `ChatViewModel` — one call to a wired runtime, then one more step (model selection) for a live chat. Errors surface as [`ManifoldKitError`](Sources/ManifoldModelCatalog/ManifoldKitError.swift).\n\n```swift\nimport SwiftUI\nimport SwiftData\nimport ManifoldKit\n\n@main\nstruct MyChatApp: App {\n    @State private var result: QuickStartResult?\n    @State private var error: ManifoldKitError?\n    @State private var showModelManagement = false\n\n    var body: some Scene {\n        WindowGroup {\n            if let result {\n                ChatView(showModelManagement: $showModelManagement)\n                    .environment(result.viewModel)\n                    .modelContainer(result.bootstrap.modelContainer)\n            } else if let error {\n                ContentUnavailableView(\"Failed to start\", systemImage: \"exclamationmark.triangle\", description: Text(error.errorDescription ?? \"\"))\n            } else {\n                ProgressView().task {\n                    do { result = try await ManifoldKit.quickStart() }\n                    catch let e as ManifoldKitError { error = e }\n                    catch { self.error = .from(error) }\n                }\n            }\n        }\n    }\n}\n```\n\n\u003e **Want a live chat on first launch with zero extra setup?** Pass `seed: .recommendedSmallModel()` — ManifoldKit downloads Qwen3-0.6B (~400 MB) in the background before returning, so the composer is generating the moment the view appears. The download is skipped when a model is already available (Foundation on iOS/macOS 26+, or a local model on disk).\n\u003e\n\u003e ```swift,no-build\n\u003e ProgressView().task {\n\u003e     do { result = try await ManifoldKit.quickStart(\n\u003e             seed: .recommendedSmallModel { progress in\n\u003e                 // update a progress indicator if desired\n\u003e             }\n\u003e         )\n\u003e     }\n\u003e     catch let e as ManifoldKitError { error = e }\n\u003e     catch { self.error = .from(error) }\n\u003e }\n\u003e ```\n\u003e\n\u003e **The chat is inert until you select a model** (without `seed:`). `quickStart()` registers the compiled-in backends but loads none, so on first run the composer reads \"No model loaded\" and the empty-state **Select Model** button only flips `showModelManagement` — nothing is presented until you attach a sheet to that binding. Fastest route: present `ModelManagementSheet` (from the opt-in `ManifoldUIModelManagement` module) with `.sheet(isPresented: $showModelManagement)`, or use `seed:` above. Step-by-step: [First-launch backend selection](docs/QUICKSTART.md#first-launch-backend-selection).\n\nSee [docs/QUICKSTART.md](docs/QUICKSTART.md) for backend selection, traits, and configuration.\nBuilding a multi-session SwiftUI app with a sidebar, persisted chats, and relaunch restore? See [docs/SWIFTUI-MULTI-SESSION.md](docs/SWIFTUI-MULTI-SESSION.md) — the canonical end-to-end guide.\nBuilding a CLI, server, or non-SwiftUI consumer? See [docs/QUICKSTART-CLI.md](docs/QUICKSTART-CLI.md) — compile-tested Foundation Models, local GGUF, and Ollama / OpenAI examples.\nRunning ManifoldKit as a standalone OpenAI-compatible server (for Cursor, Continue, or any OpenAI SDK)? Install via `brew tap roryford/manifoldkit https://github.com/roryford/ManifoldKit.git \u0026\u0026 brew install manifold-server` and see [docs/QUICKSTART-SERVER.md](docs/QUICKSTART-SERVER.md).\nWant the inference layer with a fully custom SwiftUI UI (no `ChatView`)? See [docs/QUICKSTART-BRING-YOUR-OWN-UI.md](docs/QUICKSTART-BRING-YOUR-OWN-UI.md).\nRegistering tools the model can call? See [docs/QUICKSTART-TOOLS.md](docs/QUICKSTART-TOOLS.md) — `ToolRegistry`, the local-model tool ceiling, approval gates, and streaming results.\nExposing an `AppIntent` to the model? See [docs/QUICKSTART-APPINTENTS.md](docs/QUICKSTART-APPINTENTS.md).\nFull runnable: [`Example/Examples/MinimalExample`](Example/Examples/MinimalExample).\n\n## Why ManifoldKit\n\n**Full-stack altitude.** Import one umbrella package and ship a multi-backend chat app: SwiftUI `ChatView`, the `ConversationRuntime` turn loop, SwiftData persistence, model download/management UI, and the backends — already wired together. Most alternatives hand you one layer (a UI kit, an engine wrapper, or a thin cloud client) and leave the rest as an exercise. Here the integration is the product.\n\n**Backend portability.** MLX, llama.cpp/GGUF, Apple Foundation Models, cloud (OpenAI Chat + Responses, Anthropic, Ollama, LAN), and the AnyLanguageModel bridge (Gemini, xAI, Groq, Mistral) all sit behind one `InferenceBackend` protocol. Streaming, tool calling, thinking/reasoning tokens, RAG, and structured output behave identically across every backend, so swapping engines is a config change, not a rewrite. AnyLanguageModel is wrapped as a complementary provider backend — see [How ManifoldKit compares to AnyLanguageModel](#how-manifoldkit-compares-to-anylanguagemodel).\n\n**n-1 OS reach, WWDC-ready.** ManifoldKit serves iOS 18 / macOS 15 — the installed base that Apple Foundation Models (OS-26-only, AI-hardware-gated, 4096-token cap, single fixed model) can't reach — and wraps Foundation Models as just one more backend instead of competing with it. Trait gating means one codebase yields either a ~5 MB `FoundationOnly` App Store build or the full local + cloud + RAG + voice + image-gen stack. Pre-wired stub traits (`SystemAIProviderExtension`, `CoreAI`) mean whatever Apple ships next September is one more backend, not a migration. See [CLAUDE.md → Platform policy](CLAUDE.md#platform-policy).\n\n**Reliability and security as product.** TLS pinning, SSRF and DNS-rebind guards, a throwing Keychain, a documented [threat model](docs/THREAT_MODEL.md), a fuzz harness, 6,500+ tests, capability-routed structured output, human-in-the-loop tool approval (`ToolApprovalGate`), and cost/metrics observability ship in the box. These are the things that go wrong between the demo and App Store review — see [docs/RELIABILITY.md](docs/RELIABILITY.md) for the implementation-backed guarantees.\n\nManifoldKit is **decomposable, not monolithic**: 25 libraries across a layered module graph. Take just the engine ([CLI / server path](docs/QUICKSTART-CLI.md)), just the UI (bring-your-own-runtime), or the whole stack — the umbrella is a convenience, not a requirement.\n\n## What's already in the box\n\nTable-stakes capabilities that ship today (verified in source):\n\n- **Token streaming** across every backend (`GenerationStream` / `GenerationEvent`).\n- **Multi-provider abstraction** — one `InferenceBackend` protocol, local + cloud.\n- **Tool / function calling** with a per-request tool ceiling guide for local models.\n- **Structured / typed output**, capability-routed by `StructuredOutputRouter` across GBNF, Foundation guided-generation, JSON-Schema, and JSON-prompting.\n- **Reasoning / thinking tokens** surfaced as first-class events.\n- **MCP client *and* server** ([ManifoldMCP](Sources/ManifoldMCP) + the `Server` trait).\n- **RAG with citations**, including an optional cross-encoder rerank stage (`Reranker` port; on-device `LlamaReranker` for `bge-reranker`-class GGUFs).\n- **Human-in-the-loop tool approval** via `ToolApprovalGate`.\n- **Metrics + cost estimation** for observability.\n- **On-device image generation** — `FluxDiffusionBackend` (FLUX.1 Schnell) and `MLXDiffusionBackend` (SDXL Turbo). See [docs/QUICKSTART-IMAGE-GEN.md](docs/QUICKSTART-IMAGE-GEN.md).\n\n**Status:** ManifoldKit is pre-1.0; breaking changes can land between minor versions. Deferred reliability features (e.g. mid-stream resume) are tracked in [docs/RELIABILITY.md](docs/RELIABILITY.md).\n\n## Beyond chat\n\nThe same backend, model-management, persistence, and download infrastructure that powers the chat UI is reusable for non-chat consumers. The framing is \"chat-first\" because that's the most complete reference integration, but the public surface explicitly supports:\n\n- **On-device image generation** — `FluxDiffusionBackend` (FLUX.1 Schnell, 1024×1024 in 4 steps) and `MLXDiffusionBackend` (SDXL Turbo) conform to `ImageGenerationBackend` and stream `ImageGenerationEvent`s exactly like text inference streams `GenerationEvent`. See [docs/QUICKSTART-IMAGE-GEN.md](docs/QUICKSTART-IMAGE-GEN.md) for an end-to-end example.\n- **Cloud video generation** — Any cloud service that conforms to `VideoGenerationBackend` wires into `VideoGenerationService` and `VideoGenerationRuntime`, which persist the result via `MessageStore` and expose real-time progress through `ChatViewModel.videoGenerationProgress`. The same `ManifoldBootstrap` init that accepts an `imageGenerationService` also accepts a `videoGenerationService`, so adding video is one extra parameter:\n\n  ```swift,no-build\n  let backend = MyVideoBackend()\n  backend.configure(baseURL: videoAPIURL, tokenProvider: tokenProvider, modelName: \"my-video-model\")\n  let service = VideoGenerationService(backend: backend)\n  let kit = try ManifoldBootstrap(\n      configuration: config,\n      videoGenerationService: service\n  )\n  // Trigger a generation from anywhere you hold the view model:\n  try await kit.viewModel.generateVideo(\n      prompt: \"a sun rising over mountains\",\n      config: VideoGenerationConfig(duration: 5, aspectRatio: VideoGenerationConfig.AspectRatio.landscape)\n  )\n  ```\n\n  See [docs/QUICKSTART-VIDEO-GEN.md](docs/QUICKSTART-VIDEO-GEN.md) for the full walkthrough.\n- **Standalone speech-to-text / text-to-speech** — `ManifoldVoice` wraps Apple `Speech` + `AVFoundation` behind a chat-agnostic `VoiceConversationController` that anything (image-gen prompt fields, search bars, CLI dictation) can drive. See [docs/QUICKSTART-VOICE.md](docs/QUICKSTART-VOICE.md).\n- **CLI / server / non-SwiftUI consumers** — backends, model management, and persistence work without `ChatView`. See [docs/QUICKSTART-CLI.md](docs/QUICKSTART-CLI.md).\n\n## Feature Matrix\n\nPick traits to scope which backends and capabilities ship with your build. The full trait → capability table is generated from `Sources/ManifoldKit/FeatureMatrix.swift` and rendered to [docs/FeatureMatrix.md](docs/FeatureMatrix.md).\n\nDefaults (`MLX`, `Llama`, `HuggingFace`) are enabled when you don't pass `--disable-default-traits` or a custom `traits:` array. Opt-in traits include `CloudSaaS`, `Ollama`, `MCP`, `Voice`, `Tools`, `AppIntents`, `Server`, `Macros`, `Fuzz`, and the App Store-lean `FoundationOnly`. See [docs/QUICKSTART.md → Customizing backends](docs/QUICKSTART.md#customizing-backends) for the per-trait build commands.\n\nFor a quantified breakdown of what each trait costs in binary size, build time, and dependency weight — and why the checkout is large regardless of trait set — see [docs/TRAIT-COSTS.md](docs/TRAIT-COSTS.md).\n\n## ManifoldKit vs. the field\n\nMost Swift AI projects are excellent at one layer. ManifoldKit's claim is narrow and checkable: it's the only open-source *package* that fills every column.\n\n| Project / category | Chat UI | Turn-loop runtime | Persistence | Multi-backend local + cloud | Reusable as a package |\n|---|---|---|---|---|---|\n| **ManifoldKit** | ✅ | ✅ | ✅ | ✅ | ✅ |\n| UI-only kits (Exyte/Chat, MessageKit, SwiftyChat) | ✅ | ❌ | ❌ | ❌ | ✅ |\n| Engine-only (LocalLLMClient, AnyLanguageModel, swift-transformers, LLM.swift) | ❌ | ❌ | ❌ | partial¹ | ✅ |\n| Thin cloud clients (MacPaw/OpenAI, SwiftAnthropic) | ❌ | ❌ | ❌ | ❌ (one provider) | ✅ |\n| Apple Foundation Models | ❌ | ❌ | ❌ | ❌ (one capped model, OS 26+) | ✅ |\n| Full-stack apps (fullmoon, Enchanted) | ✅ | ✅ | ✅ | partial | ❌ (fork, not a package) |\n\n¹ LocalLLMClient is the closest multi-engine analog (multiple local engines behind one interface) but ships no UI, persistence, or cloud backends.\n\nEach row is genuinely strong at its own layer — a UI kit renders beautiful bubbles, a cloud client is a clean SDK, Foundation Models is free and on-device. The point isn't that they're weak; it's that assembling them into a shipping chat product is the work ManifoldKit already did. Cross-language demand is proven (React's assistant-ui sees ~200k downloads/month; Vercel ships a chatbot template) — there is no Swift equivalent until this one. Full rationale in [docs/POSITIONING.md](docs/POSITIONING.md).\n\n## Install\n\n```swift\n.package(\n    url: \"https://github.com/roryford/ManifoldKit.git\",\n    from: \"0.47.0\" // x-release-please-version\n)\n```\n\nMost apps add a single product — the `ManifoldKit` umbrella — which re-exports the runtime, persistence, backends, UI, and inference surface in one import:\n\n```swift\n.target(name: \"MyApp\", dependencies: [\n    .product(name: \"ManifoldKit\", package: \"ManifoldKit\"),\n])\n```\n\nSpecialised modules (`ManifoldUIModelManagement`, `ManifoldMCP`, `ManifoldVoice`, `ManifoldHuggingFace`, `ManifoldAppIntents`) stay opt-in — add them explicitly when you need that surface. `ManifoldVoice` in particular is usable outside chat: it wraps Apple `Speech` / `AVFoundation` behind a chat-agnostic `VoiceConversationController`, so anything from an image-gen prompt field to a CLI dictation tool can drive it. See [docs/QUICKSTART-VOICE.md](docs/QUICKSTART-VOICE.md) for the standalone STT path; the chat composer accessory is the *other* consumer of the same controller. For finer-grained dependency control (e.g. a UI-only target that doesn't link `ManifoldBackends`), depend on the individual products instead. See [docs/QUICKSTART.md](docs/QUICKSTART.md) for trait selection and the bring-your-own-UI path.\n\n## Requirements\n\n- **Swift 6.1+** (`swift-tools-version: 6.1` in this package's `Package.swift`)\n- If your app's own manifest declares `.macOS(.v26)` / `.iOS(.v26)`, use **Swift 6.2+** there — those platform entries were introduced in PackageDescription 6.2.\n- iOS 18+ / macOS 15+\n- Apple Foundation Models require iOS 26+ / macOS 26+\n\nManifoldKit follows an **n-1 platform policy**: the current Apple OS release and the one immediately before it. When Apple ships a new major OS each September, both minimums bump by one. See [CLAUDE.md → Platform policy](CLAUDE.md#platform-policy) for the rationale.\n\n## Demo\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"Example/Screenshots/demo-macos.png\" alt=\"ManifoldKit on macOS — chat with streaming response and session sidebar\" width=\"58%\"\u003e\n  \u003cimg src=\"Example/Screenshots/demo-ios.png\" alt=\"ManifoldKit on iOS — chat conversation on iPhone\" width=\"36%\"\u003e\n\u003c/p\u003e\n\nStart with [`Example/Examples/MinimalExample`](Example/Examples/MinimalExample) if you're new — it's the canonical Hello World. The full-featured reference app lives at [`Example/Advanced`](Example/Advanced) (sessions, model management, custom composer accessories); open it once the minimal example makes sense.\n\n## Architecture\n\nManifoldKit ships **25 libraries**, **3 executables**, and **1 macro plugin**. The core runtime stack is six libraries; the rest are optional sibling modules and test-only targets gated behind SwiftPM traits.\n\n```\nManifoldVoice              ManifoldUIModelManagement\n(Voice trait)              (model browser + endpoint UI)\n        │                          │\n        └────────► ManifoldUI ◄────┘\n                       │\n                       ▼\n            ManifoldPersistenceSwiftData\n            (SwiftData schema, ManifoldBootstrap)\n                       │\n                       ▼\n                 ManifoldRuntime\n                 (Ports, use cases, ConversationRuntime)\n                       │\n                       ▼\n                ManifoldInference  ◄─── ManifoldBackends\n                (Protocols, services)   (MLX, llama.cpp,\n                       ▲                 Foundation, Cloud)\n                       │\n                ManifoldMCP\n                (MCP descriptors, client, tool bridge)\n```\n\n`ManifoldBackends` and `ManifoldMCP` depend on `ManifoldInference` **directly**, not via `ManifoldRuntime` — that keeps both modules free of SwiftData so host apps can wire backends or MCP into a non-SwiftData runtime. The full target list lives in [CLAUDE.md → Targets](CLAUDE.md#targets).\n\n### Turn-loop orchestration\n\n`ConversationRuntime` (`Sources/ManifoldRuntime/Services/ConversationRuntime.swift`) is the **single turn loop** for chat. It owns all turn-flow operations — send, regenerate, edit, cancel, and branch — dispatched through `processTurn(TurnInput(...))` with the corresponding `TurnKind` case. There is no alternative path. Host apps get a configured runtime from `ManifoldBootstrap` (exposed as `bootstrap.conversationRuntime`) and forward user actions to it. See [CONTRIBUTING.md → Architecture invariants](CONTRIBUTING.md#architecture-invariants) for the full list of dependency rules the lint enforces.\n\n## Supported Model Types\n\n| Type | Backend | Format | Source | Image input |\n|------|---------|--------|--------|-------------|\n| GGUF | `LlamaBackend` (llama.cpp) | Single `.gguf` file | HuggingFace, local | Not yet; tracked in [#416](https://github.com/roryford/ManifoldKit/issues/416) |\n| MLX | `MLXBackend` (mlx-swift) | Directory with `config.json` + `.safetensors` | HuggingFace, local | Vision models only |\n| Foundation | `FoundationBackend` | `ModelInfo.builtInFoundation` (built-in, no download) | Apple Intelligence | No public FoundationModels image-input API yet |\n| OpenAI | `OpenAIBackend` | Cloud API | api.openai.com | Vision-capable models |\n| Claude | `ClaudeBackend` | Cloud API | api.anthropic.com | Vision-capable models |\n| Ollama | `OpenAIBackend` | Local API | localhost:11434 | Vision-capable OpenAI-compatible models |\n| LM Studio | `OpenAIBackend` | Local API | localhost:1234 | Vision-capable OpenAI-compatible models |\n\n### Model storage scoping\n\n`ModelStorageService()` stores and discovers local models under `\u003cApplication Support\u003e/\u003cManifoldConfiguration.shared.bundleIdentifier\u003e/\u003cmodelsDirectoryName\u003e` by default. This keeps multiple ManifoldKit-based apps on the same machine from seeing each other's downloaded models. Hosts that intentionally share a model pool can opt in by passing `ModelStorageService(baseDirectory: sharedModelsDirectory)`.\n\nDiscovery additionally surfaces any `.gguf` files (or MLX model directories) in `~/Documents/Models` so users who follow the [`CLI quickstart`](docs/QUICKSTART-CLI.md) and drop files there see them in the SwiftUI `ModelManagementSheet` without extra setup. App-scoped storage always wins on a collision. See [`docs/LOCAL-GGUF.md`](docs/LOCAL-GGUF.md) for the full storage contract and the typed error surface (`ModelDiscoveryError`) the sheet uses to explain load failures.\n\n## Key Types\n\n| Type | Purpose |\n|------|---------|\n| `ManifoldKit.quickStart` | One-call bootstrap — returns `QuickStartResult { bootstrap, viewModel }`. |\n| `ManifoldBootstrap` | SwiftData-backed bootstrap — installs configuration, builds persistence adapters, holds shared services. Drop down to this when you need a custom inference service or model container. |\n| `ChatViewModel` | Central chat controller — messages, generation, model loading, settings. |\n| `SessionManagerViewModel` | Chat session CRUD and selection. |\n| `ModelManagementViewModel` | HuggingFace search, downloads, local model management (`ManifoldUIModelManagement`). |\n| `InferenceService` | Backend orchestrator — selects and delegates to the right backend. |\n| `ConversationRuntime` | Single turn loop — all turn-flow operations dispatched via `processTurn(TurnInput(...))` with `ConversationEvent` hooks. |\n| `ChatView` | Main chat interface with message list and input bar. |\n| `SessionListView` | Sidebar session list with rename/delete. |\n| `ModelManagementSheet` | Combined model browser + storage management. |\n| `InferenceBackend` | Common interface for all inference engines — implement this to add a custom backend. |\n| `ManifoldKitError` | Unified error rim — every public throws normalises to this type. |\n\nFor the full surface (protocols, services, views), browse `Sources/` or read the DocC catalogues in each module's `*.docc/` directory.\n\n## Tool Calling\n\n\u003e [!WARNING]\n\u003e The `@ToolSchema` macro is gated behind the `Macros` SwiftPM trait (default-off). Default builds skip swift-syntax (~647 source files) and `@ToolSchema` is invisible. To use the macro, opt in with `--traits Macros`. Without it, declare `JSONSchemaValue` by hand on `ToolDefinition.parameters`.\n\nRegister tools with `ToolRegistry` and pass `toolRegistry.definitions` as `GenerationConfig.tools`:\n\n```swift,no-build\nlet registry = ToolRegistry()\nregistry.register(MyWeatherTool())\n\nlet (_, stream) = try inferenceService.enqueue(\n    messages: history,\n    tools: registry.definitions\n)\n```\n\n**Local backend tool ceiling:** Local instruct models (3B–8B) degrade sharply when given more than ~5 tool definitions per request. For cloud backends (OpenAI, Anthropic, large Ollama models) 20+ tools is fine. When targeting a local backend, curate tools per request and keep definitions at or below 5 per call.\n\nFor the complete guide — tool definition shape, `TypedToolExecutor`, streaming tool results, approval gates, and the `preToolUseHook` — see [docs/QUICKSTART-TOOLS.md](docs/QUICKSTART-TOOLS.md).\n\n## MCP\n\n```swift,no-build\nimport ManifoldInference\nimport ManifoldMCP\n\nlet client = MCPClient()\nlet source = try await client.connect(descriptor)\nawait source.register(in: registry)\n```\n\nFor a complete walkthrough (descriptor setup, lifecycle, and built-in catalog), see `Sources/ManifoldMCP/ManifoldMCP.docc/Articles/MCPGettingStarted.md`.\n\nManifoldKit also supports running as an **MCP server** — exposing your app's live state and tools to external MCP clients such as Claude Desktop, other agents, or any MCP-aware host. Import `ManifoldMCPHost` and follow the setup guide at `Sources/ManifoldMCPHost/ManifoldMCPHost.docc/Articles/MCPHostServer.md`. This is the entry point for agent-platform builders who want to surface their app's capabilities to the broader MCP ecosystem rather than consuming external tools.\n\n## Skills, Handoffs, and Hooks\n\nThree session-scoped extension points complement MCP for non-MCP hosts:\n\n- **ManifoldSkills** — filesystem-discovered Claude-Code-compatible `SKILL.md` skills, exposed to the model via a single `invoke_skill` dispatch tool. See `Sources/ManifoldSkills/ManifoldSkills.docc/Articles/SkillsGettingStarted.md`.\n- **Agent handoffs** — multi-persona sessions where the model emits `transfer_to_\u003cname\u003e` to swap the active agent. See `Sources/ManifoldRuntime/ManifoldRuntime.docc/Articles/AgentHandoffs.md`.\n- **Hook system** — synchronous `preToolUse` (sanitize/block) and `preCompact` (observe) hooks distinct from the observational event stream. See `Sources/ManifoldRuntime/ManifoldRuntime.docc/Articles/HookSystem.md`.\n\n## Custom Backends\n\nImplement `InferenceBackend` and register it. The protocol takes a precomputed `ModelLoadPlan` so the caller's memory-admission verdict and effective context size flow through to the backend instead of being recomputed:\n\n```swift,no-build\nclass MyBackend: InferenceBackend, @unchecked Sendable {\n    var isModelLoaded = false\n    var isGenerating = false\n    var capabilities: BackendCapabilities { /* ... */ }\n\n    func loadModel(from url: URL, plan: ModelLoadPlan) async throws { /* ... */ }\n    func generate(prompt: String, systemPrompt: String?, config: GenerationConfig)\n        throws -\u003e GenerationStream { /* ... */ }\n    func stopGeneration() { /* ... */ }\n    func unloadModel() { /* ... */ }\n}\n\ninferenceService.registerBackendFactory { modelType in\n    switch modelType {\n    case .gguf: return MyBackend()\n    default: return nil\n    }\n}\n```\n\n`plan.effectiveContextSize` carries the resolved context window and `plan.verdict` is one of `.allow` / `.warn` / `.deny`. Callers must check the verdict before invoking `loadModel`; conformers may rely on that precondition.\n\n## Cloud API Configuration\n\nCloud endpoints flow through storage-neutral `APIEndpointRecord` values. `APIConfigurationView` persists records through the runtime's `EndpointStore`:\n\n```swift,no-build\nlet endpoint = APIEndpointRecord(\n    name: \"My OpenAI\",\n    provider: .openAI,\n    baseURL: \"https://api.openai.com\",\n    modelName: \"gpt-4o-mini\"\n)\ntry KeychainService.store(key: \"sk-...\", account: endpoint.keychainAccount)\ntry await runtime.endpointStore.insertEndpoint(endpoint)\n```\n\n`KeychainService.store` / `.delete` and the SwiftData `APIEndpoint.setAPIKey` / `.deleteAPIKey` helpers throw `KeychainError` on failure. Deleting a non-existent item is non-throwing (`errSecItemNotFound` is treated as success), so `tearDown` / `deinit` cleanup can keep its `try?` idiom.\n\n## Prompt Templates\n\nGGUF models require explicit chat formatting. ManifoldKit includes templates for ChatML, Llama 3, Mistral, Alpaca, Gemma, and Phi. Templates auto-detect from GGUF metadata when available. User content is sanitised to strip special tokens and prevent prompt injection.\n\n## Security\n\nSee [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md) for the full threat model. A quick summary:\n\n- API keys stored in Keychain with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`.\n- Keys read just-in-time from Keychain rather than cached as long-lived properties; during an in-flight `URLSession` request the key bytes do exist in process memory as a Swift `String` and are not zeroized after use (see [docs/FIPS.md](docs/FIPS.md) §non-mitigations).\n- Certificate pinning via `PinnedSessionDelegate`; `api.openai.com` and `api.anthropic.com` fail closed if pin sets are missing/empty. Custom hosts use platform trust by default or can be hardened to fail-closed via `ManifoldConfiguration.shared.customHostTrustPolicy = .requireExplicitPins`.\n- HTTPS enforced for non-localhost endpoints.\n- User content sanitised in prompt templates to prevent injection.\n- Sensitive data uses `privacy: .private` in `os.Logger` calls; error response bodies filtered before logging.\n\nFor regulated deployments (healthcare, federal-adjacent, finance), see [docs/FIPS.md](docs/FIPS.md) for the full answer to \"are your cryptographic primitives FIPS 140-3 validated?\".\n\n## Binary Dependencies\n\n`ManifoldBackends` includes two pre-built binary xcframeworks:\n\n- **llama.swift** — wraps a pre-built llama.cpp xcframework. For source-verified builds, follow the [llama.swift build instructions](https://github.com/mattt/llama.swift) to compile your own.\n- **mlx-swift** — Apple's MLX framework ships as a pre-built xcframework from [ml-explore/mlx-swift](https://github.com/ml-explore/mlx-swift). Source builds supported via that upstream repo.\n\nBoth are pinned to specific tagged releases in `Package.swift` — review `Package.resolved` for the exact versions in use.\n\n## Troubleshooting\n\n### \"XCFramework Info.plist not found\" or \"workspace-state.json desync\"\n\nThis typically happens after changing the active trait set. SwiftPM caches binary-target paths in `.build/workspace-state.json` and does not auto-re-resolve stale paths. Run:\n\n```bash\nscripts/clean-build.sh\n```\n\n### Stale \"No such module 'ManifoldPersistenceSwiftData'\" in the editor\n\nSourceKit can retain stale module-not-found diagnostics from a previous trait-set build. Restart the SourceKit language server (Xcode: *Product → Clean Build Folder*, then reopen; VS Code: \"Swift: Restart SourceKit-LSP\" from the command palette). If that's insufficient, run `scripts/clean-build.sh`. For non-destructive investigation see `docs/SOURCEKIT_DIAGNOSTICS.md`.\n\n## Example App\n\nStart with [`Example/Examples/MinimalExample`](Example/Examples/MinimalExample) — the canonical Hello World. The full-featured reference app lives at [`Example/Advanced`](Example/Advanced); open it once the minimal example makes sense.\n\n```bash\ncd Example\nopen Advanced.xcodeproj\n```\n\n## How ManifoldKit compares to AnyLanguageModel\n\nAnyLanguageModel is HuggingFace's Swift package — it mirrors Apple's `FoundationModels` API and exposes many providers behind a single protocol. ManifoldKit and AnyLanguageModel occupy adjacent niches: AnyLanguageModel optimises for provider coverage and API familiarity; ManifoldKit optimises for production reliability and drop-in chat UI (`ChatView` + `SessionListView` + `ModelManagementSheet` on day one). Pick the one whose axis matches the problem you're solving.\n\nManifoldKit also **consumes** AnyLanguageModel as a backend: the `AnyLanguageModel` trait is the supported path for providers without a native backend — Gemini, xAI, Groq, Mistral, OpenRouter, and any OpenAI/Anthropic-compatible endpoint — so they plug into the same `ChatViewModel` and runtime as a native backend. See [docs/PROVIDER-BRIDGE.md](docs/PROVIDER-BRIDGE.md) for the provider list, URL/trait setup, and capability limits.\n\n## Migrating from BaseChatKit\n\nThis package was renamed from `BaseChatKit` to `ManifoldKit` in v0.20. The old GitHub URL redirects, but:\n\n- Update SPM dependencies to `.package(url: \"https://github.com/roryford/ManifoldKit.git\", ...)` with `from: \"0.47.0\"` \u003c!-- x-release-please-version --\u003e\n- Update imports: `import BaseChatKit` → `import ManifoldKit` (and similarly for sub-modules).\n- Renamed public types: `BaseChatBootstrap` → `ManifoldBootstrap`, `BaseChatConfiguration` → `ManifoldConfiguration`, `BaseChatSchemaV3/4/5` → `ManifoldSchemaV3/4/5`, `BaseChatMigrationPlan` → `ManifoldMigrationPlan`, `BaseChatBackgroundTaskIdentifiers` → `ManifoldBackgroundTaskIdentifiers`.\n- **BREAKING — local SwiftData stores reset.** Apps upgrading from 0.19.x create fresh databases on first launch. We chose this clean break over preserving data with `@Model.originalName` because v0.20 is pre-1.0.\n- Cache directories `~/Library/Caches/BaseChatKit/` and `~/Library/Application Support/BaseChatKit/` are orphaned; users get fresh state.\n- Background-task identifiers `com.basechatkit.background.*` → `com.manifoldkit.background.*` — update `BGTaskSchedulerPermittedIdentifiers` in Info.plist.\n\n## License\n\nMIT License. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froryford%2Fmanifoldkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froryford%2Fmanifoldkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froryford%2Fmanifoldkit/lists"}