Skip to content

coach: on-device AI Coach — bundled local LLM via llama.cpp (iOS)#553

Open
hkuehl wants to merge 1 commit into
ryanbr:mainfrom
hkuehl:coach-clean
Open

coach: on-device AI Coach — bundled local LLM via llama.cpp (iOS)#553
hkuehl wants to merge 1 commit into
ryanbr:mainfrom
hkuehl:coach-clean

Conversation

@hkuehl

@hkuehl hkuehl commented Jul 17, 2026

Copy link
Copy Markdown

What

Adds a fifth AI Coach provider, .onDevice, that runs a small LLM entirely on the iPhone via in-process llama.cpp (Metal) — no API key, and after a one-time model download, no network. It becomes the default, front-and-centre coach on iOS; the existing cloud BYOK providers (OpenAI / Anthropic / Gemini / Custom) are unchanged.

  • Model: Llama-3.2-3B-Instruct (Q4_K_M GGUF, ~2 GB), fetched on first run from a pinned Hugging Face commit revision and SHA-256 verified. Never bundled or redistributed — the repo commits only a URL + checksum (ModelCatalog.swift).
  • Runtime: pinned prebuilt llama.cpp xcframework (official ggml-org release b9947) as an SPM binary target with URL + checksum pinned (Packages/LlamaCpp).
  • Integration: conforms to the existing AIProviderClient protocol, so the whole coach pipeline — context building, consent gating, chat history, CoachView — is reused. iOS-only (LlamaEngine / OnDeviceClient excluded from the macOS target; provider hidden on macOS).

Offline-by-design note

Coach inference is 100% on-device. The only network activity is the one-time, user-initiated download of the public model weights (opposite direction — no user data leaves the device). This is an extension of the already opt-in AI Coach, the project's existing single networked feature; the offline-first posture is preserved.

Memory & stability (iOS)

  • com.apple.developer.kernel.increased-memory-limit entitlement; ~6 GB-RAM device gate (ModelCatalog.deviceMeetsRequirements).
  • Frees the model on critical memory pressure (when idle) and on backgrounding, reloading lazily — so the coach is never the top jetsam target.
  • Streaming token generation with Stop (Task cancellation). KV cache is cleared before each prefill (reused-context turns were overrunning n_ctx). Flash Attention enabled, n_ubatch / n_threads set, and streaming UI updates coalesced to ~16 Hz (was an O(n²) MarkdownUI re-parse per token).
  • Model download is resumable (continues a dropped transfer instead of restarting ~2 GB); weights live in Application Support, excluded from backup.

Licensing / attribution

  • NOTICE updated: llama.cpp / ggml (MIT) and the downloaded Llama 3.2 model (Meta Llama 3.2 Community License — not redistributed).
  • "Built with Llama" attribution + license / Acceptable-Use-Policy links shown in the coach setup UI. The weights are downloaded on request, not shipped, so NOOP does not redistribute them.

Verification

  • Compiled NOOPiOS locally against the real b9947 headers (the app-build workflow is disabled by default, so this isn't covered by CI). The macOS Strand scheme compiles too (on-device path excluded there).
  • Coach unit tests green (StrandTests: provider gating, model catalog, download manager, streaming adapters).
  • Device-tested on a physical iPhone (≥ 6 GB): download → SHA-256 verify → stream a reply → Stop mid-stream → multi-turn chat → background unload/reload → delete reclaims ~2 GB.
  • UI uses only StrandDesign tokens. No BLE / sync-path changes. iOS-only — no Android code, so the cross-platform parity contract doesn't apply here.

MARKETING_VERSION bumped to 9.1.0 (new feature; SemVer MINOR).

🤖 Generated with Claude Code

- New .onDevice provider backed by in-process llama.cpp (pinned b9947 xcframework
  via the LlamaCpp SPM package), first-run download of Llama-3.2-3B-Instruct
  Q4_K_M, streaming token generation. iOS default provider; hidden on macOS.
- ModelDownloadManager (streaming SHA-256 verify), ModelCatalog, OnDeviceClient,
  LlamaEngine actor, CoachView setup card, increased-memory entitlement.
- Fix: clear the KV cache before each prefill so reused-context turns start at
  position 0 (cumulative positions were overrunning n_ctx and killing the coach
  after ~3 turns; also fixes attending to duplicated context on earlier turns).
- Fix: the Coach toolbar gear now opens a settings sheet (provider / model / key)
  instead of a "Disconnect" action that was a silent no-op for the on-device
  provider — so settings stay reachable once the model is downloaded.
- Performance pass: coalesce streaming UI updates (~16 Hz flush, not a full
  MarkdownUI re-parse per token — was O(n^2) over the reply); pin n_ubatch, enable
  Flash Attention, and set n_threads on the llama context; size the chat-template
  buffer exactly (no silent prompt truncation); resumable model download (continues
  a dropped transfer instead of restarting ~2 GB).
- License compliance: NOTICE entries for llama.cpp (MIT) and the downloaded Meta
  Llama 3.2 model (Community License, not redistributed); "Built with Llama"
  attribution + license/AUP links in the coach setup UI (palette-tinted); model
  download URL pinned to an immutable Hugging Face commit revision (sha256 still
  fail-closed). Bump MARKETING_VERSION to 9.1.0 (new feature; SemVer MINOR).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant