Architecture
Layers, capability seams, the TaskStore facade and the rules that keep the engine headless and testable.
Layers
SwiftUI app (iPhone, iPad, Mac, Watch, widgets)
│
TaskUI · TaskIntents · TaskAdapters
│
TaskCore ── TaskStore facade ── SwiftData (+ CloudKit private database)
│ │
│ ├── Planning: DayPlanner, PlanRouter, TimeBlocker
│ ├── AI: TaskNLParser, TaskToolkit, NLBackend
│ ├── Agents: AgentGate, approvals, receipts, keys
│ └── IO: .unkt bundles, .xlsx export
│
task-mcp (stdio, optional HTTP)
TaskCore links no SwiftUI or UIKit. Anything with a side effect on the operating system is injected through a protocol, so the core runs the same in the app, in tests and inside the MCP server.
Capability seams
| Protocol | Purpose | Implementations |
|---|---|---|
TaskSyncProvider | Push and pull changes with any backend | CloudKitTaskSyncProvider; NoSyncProvider for local use and tests |
TaskNotificationScheduling | Due-date and early-warning notifications | UserNotificationTaskScheduler in TaskAdapters |
TaskGeofencing | Arrive and leave reminders | CoreLocationGeofencer in TaskAdapters |
AttachmentStore | Attachment bytes, kept outside the database | FileAttachmentStore on the device; ICloudDriveAttachmentStore to sync bytes through your own iCloud Drive |
AttachmentTransport | Optional upload path for a team backend | S3PresignedTransport and HTTPUploadTransport; the host signs URLs, so the engine never holds cloud credentials |
CalendarProviding | Free and busy time for planning | Supplied by the app from EventKit; EmptyCalendarProvider for tests |
TemplateProviding | Task blueprints | DefaultTemplateStore |
DayPlanProvider | An optional AI planner | Apple Intelligence, a paired device, a local server or your own cloud key (see Planning) |
NLBackend | Optional language model for capture | LLMTaskBackend (any OpenAI-compatible endpoint, your key) |
The facade
Humans and agents use the same TaskStore. A minimal setup:
import TaskCore
let store = try TaskStore(
configuration: .cloudKit(containerID: "iCloud.com.purvakalpa.Tasker"),
syncProvider: CloudKitTaskSyncProvider(containerID: "iCloud.com.purvakalpa.Tasker"),
crypto: TaskCrypto.fromKeychain()) // seals notes and page bodies, see Security
try store.bootstrap() // default list, built-in workflow stages, personal workspace; idempotent
try await store.startSync()
let task = try store.createTask(TaskDraft(title: "Ship 1.2", estimatedMinutes: 90, priority: .high))
let plan = try await store.planDay(budgetMinutes: 180, preferredEnergy: .focused)
Configurations are .inMemory for tests and previews, .local() for on-device only, and .cloudKit(containerID:) for iCloud sync. An app that already owns a ModelContainer can splice in TaskSchema.models and pass its container instead. Notes and page bodies are encrypted by default when a TaskCrypto is supplied; attachment encryption is an opt-in flag.
Errors
Store operations throw TaskStoreError: notBootstrapped, listNotFound, taskNotFound, stageNotFound, cyclicParent, attachmentIngestFailed, syncUnavailable, importMalformed and persistence. Background sync never throws: failures surface on the syncStatus stream so the UI can show a badge. An explicit “Sync now” rethrows.
Concurrency and stability
- Built for Swift 6 strict concurrency. Value types (
TaskDraft,JSONValue, enums) areSendable;@Modelobjects are not, so code passes IDs across actors, never models. - Semantic versioning. Before 1.0, minor versions may break the API; patch versions never do.
- Persisted schemas are frozen per version and checked in CI (see Data and sync).
Tests
TaskCoreTests cover recurrence, the conflict resolvers, task hierarchy and cycle safety, store round-trips, the day planner and its router, time blocking, reviews and streaks, calendar free-window maths, geofence distance, the natural-language parser, the tool kit, crypto, schema locks, the agent gate matrix, scheduled delegation and MCP golden fixtures. They run with swift test on macOS; SwiftData, CloudKit and App Intents are not available on Linux.