A privacy-first, Apple-ecosystem AI assistant that acts like a real assistant: it makes phone calls, browses the web, controls your smart home, manages reminders, screens unknown callers, and chats with you over iMessage — all while keeping every learned fact on your own device.
┌─────────────────────────────────────────────────┐
│ NOBS Apps │
│ iOS App │ macOS App │ watchOS Complication │
└────────────┬────────────────────────────────────┘
│ Swift Package (NOBSKit)
┌────────────▼────────────────────────────────────┐
│ NOBSAssistant ◄── Central Coordinator │
│ Routes intents to the right module │
└──┬──────┬──────┬──────┬──────┬──────┬──────────┘
│ │ │ │ │ │
NOBSCore │ NOBSCallKit │ NOBSHomeKit │
Local AI │ (CallKit + │ (HomeKit) │
Client │ Google Voice│ │
│ │ │
NOBSiMessage NOBSDatabase NOBSReminders
(Messages.app (Work / Personal (EventKit)
integration) CoreData stores)
│
NOBSSecurity
(Keychain + LocalAuth)
| Principle | Implementation |
|---|---|
| On-device privacy | All learned context is stored in encrypted Core Data databases on the user’s device. Nothing is sent to third-party clouds. |
| Local model | The AI runs on a user-owned server. Apps communicate via a local network REST/WebSocket API (NOBSCore). |
| Work / Personal separation | Two isolated Core Data persistent stores — every piece of information is tagged to a context at write time. |
| Modular | Each capability lives in its own Swift target so individual features can be shipped, tested, and updated independently. |
| Keychain for all secrets | OAuth tokens and API keys are stored in the iOS/macOS Keychain via NOBSSecurity.KeychainStore with .afterFirstUnlockThisDeviceOnly protection so secrets never migrate off device. |
| Biometric gate | NOBSSecurity.LocalAuthGate wraps LAContext to enforce Face ID, Touch ID, or passcode authentication before sensitive operations (calls, messages, HomeKit) are executed. |
NOBSCore — AI Model ClientConnects to a locally hosted LLM server (Ollama, LM Studio, or any OpenAI-compatible endpoint).
ModelClient — async HTTP client for chat completionsPromptBuilder — assembles system prompts with user contextIntentParser — extracts typed AssistantIntent values from model JSON outputAssistantIntent — enum of every action the assistant can performNOBSAssistant — Central CoordinatorReceives AssistantIntent values and dispatches them to the right module handler.
IntentHandler protocol — implement to add new capabilitiesIntentRouter — finds the right handler for each intentNOBSCallKit — Phone Calls & Call ScreeningCXCallControllerCallScreener — contact-list-based decision engineNOBSVoiceNOBSiMessage — iMessage IntegrationConversationHistory stored in NOBSDatabaseNOBSHomeKit — Smart HomeHomeKit (HMHomeManager) to enumerate and control accessoriesNOBSDatabase — Work / Personal Data StoresNSPersistentContainer stacks (never shared)NSPersistentStoreFileProtectionKeyNSPersistentCloudKitContainer syncs via CloudKit (see iCloud Sync below)MemoryRepository — store and search on-device learned factsTaskRepository — create and complete user tasksNOBSSecurity — Keychain & Biometric AuthenticationProvides the security primitives used throughout the NOBS suite.
KeychainStore — type-safe wrapper around Apple’s SecItem APIs; items stored with .afterFirstUnlockThisDeviceOnly so secrets never migrate off the deviceLocalAuthGate — LAContext-based gate requiring Face ID, Touch ID, or device passcode before sensitive operations proceed; successful authentications are cached for a configurable session durationVoiceTokenStore (in NOBSVoice) — Keychain-backed persistence for Google Voice OAuth tokens so tokens survive app restarts without re-authorizationNOBSReminders — Reminders & EventKitEventKitnobs-context:personal or nobs-context:workrequestAccess APIsNOBSVoice — Google Voice APIVoiceIntentHandler bridges Google Voice into the NOBS intent pipeline# Clone the repo
git clone https://github.com/acburgess25/NOBS.git
cd NOBS
# Open in Xcode
open Package.swift # library-only work
Edit Sources/NOBSCore/ModelClient.swift:
ModelConfiguration.localhost // http://127.0.0.1:11434 by default
// or supply your server IP:
ModelConfiguration(localEndpoint: URL(string: "http://192.168.1.10:11434")!)
Install Ollama on your server and pull a model:
ollama pull llama3
VoiceClient (store clientID / clientSecret in the iOS Keychain — never in source files).In your Xcode app target, enable:
HomeKitSiriCallKit (com.apple.developer.callkit)iMessage ExtensionAdd NSRemindersUsageDescription to Info.plist.
⚠️ iCloud Sync is OFF by default and must be explicitly enabled by the user.
By default, NOBS stores everything on-device only. iCloud sync is an opt-in feature that must be presented to the user with a clear disclosure before activation.
| On-Device Only (default) | iCloud Sync | |
|---|---|---|
| Data stored on your device | ✅ | ✅ |
| Data uploaded to Apple’s servers | ❌ | ✅ |
| Syncs across your Apple devices | ❌ | ✅ |
| Apple’s iCloud Privacy Policy applies | ❌ | ✅ |
| Work & Personal kept separate | ✅ | ✅ (separate CloudKit zones) |
// 1. Show the disclosure FIRST — required before enabling iCloud.
print(iCloudDisclosure.userFacingWarning)
// or use iCloudDisclosure.fullExplanation for a dedicated settings screen
// 2. Only after the user explicitly confirms:
try NOBSDatabase.shared.setup(
storageMode: .iCloud(containerID: "iCloud.com.yourcompany.nobs")
)
iCloudDisclosure provides three ready-made strings you must present before enabling iCloud:
iCloudDisclosure.userFacingWarning — short alert body (use in a confirmation dialog).iCloudDisclosure.fullExplanation — long explanation for a dedicated “About iCloud Sync” screen.iCloudDisclosure.statusLine(for:) — one-line status for settings footers and banners.Add to your app target in Xcode:
iCloud capability with your CloudKit containercom.apple.developer.icloud-container-identifiers entitlementNSUbiquitousContainers key in Info.plistNOBSSecurity.KeychainStore)All sensitive string values — OAuth access tokens, refresh tokens, and token expiry dates — are stored in the system Keychain using Apple’s SecItem APIs.
// In production always use the default VoiceTokenStore (backed by the Keychain):
let client = VoiceClient(credentials: creds) // tokenStore defaults to VoiceTokenStore()
// In unit tests, pass nil to skip Keychain access:
let client = VoiceClient(credentials: creds, tokenStore: nil)
Protection class used: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
| Property | Value |
|---|---|
| Accessible after first unlock | ✅ (background app access OK) |
| Migrates to new device via backup | ❌ |
| Syncs via iCloud Keychain | ❌ |
| Available on iOS Simulator | ✅ |
NOBSSecurity.LocalAuthGate)Wrap any sensitive operation with LocalAuthGate to require Face ID, Touch ID, or the device passcode.
let gate = LocalAuthGate(
policy: .biometricOrPasscode, // or .biometricOnly
reason: "Authenticate to place a call",
sessionDuration: 300 // re-authenticate after 5 minutes
)
// In your app / view model:
try await gate.requireAuthentication()
// … perform sensitive operation …
// When app goes to background — force re-authentication next time:
await gate.invalidate()
VoiceTokenStore, never in source files or UserDefaults.