Skip to content

iOS Agent — Background Execution & Compute Modes ​

iOS workers can contribute compute in three modes with full background execution and Bluetooth-local fallback. This page documents the compute mode system, background task scheduling, power-aware policy, and Bluetooth Local transport.

Compute modes ​

ModeDescriptionRewards
OffApp is idle; no compute offered.None
OnConnects to the internet swarm via coordinator heartbeat loop.Yes
Bluetooth LocalAdvertises compute over CoreBluetooth to nearby Mac nodes; no internet required.No

Mode is controlled via the three-way picker in the Swarm tab (ComputeMode enum). The selected mode is persisted to UserDefaults and restored on next launch.

Automatic mode downgrade ​

When the device is in On mode and the network path becomes unsatisfied (Wi-Fi drops, airplane mode), NWPathMonitor automatically switches the agent to Bluetooth Local mode and logs an event. When internet returns, the app notifies the user but does not auto-escalate back — the user must switch back to On manually to avoid silently re-enrolling in the rewards network.

Background execution ​

UIBackgroundModes declared ​

xml
<key>UIBackgroundModes</key>
<array>
  <string>fetch</string>
  <string>processing</string>
  <string>bluetooth-central</string>
  <string>bluetooth-peripheral</string>
</array>

BGTaskScheduler tasks ​

Task IDTypePurpose
io.edgecoder.ios.runtimeBGProcessingTaskLong-running compute burst; keeps heartbeat loop alive while backgrounded. Re-schedules itself on expiry for continuous coverage.
io.edgecoder.ios.heartbeatBGAppRefreshTaskShort-lived 15-minute wake for a single heartbeat when the processing task is not running.

Both tasks are registered in AppDelegate.application(_:didFinishLaunchingWithOptions:). Background tasks are scheduled automatically when the app enters the background (via UIApplication.didEnterBackgroundNotification) if computeMode != .off.

Scheduling flow ​

App → Background
  └─ if computeMode != .off:
       BGProcessingTaskRequest(id: "io.edgecoder.ios.runtime", requiresNetworkConnectivity: false)
       BGAppRefreshTaskRequest(id: "io.edgecoder.ios.heartbeat", earliestBeginDate: +15min)

iOS grants BGProcessingTask:
  └─ sendHeartbeatIfNeeded() every 15s
  └─ Re-schedules itself before expiry
  └─ task.setTaskCompleted(success:) on cancellation

iOS grants BGAppRefreshTask:
  └─ One sendHeartbeatIfNeeded() call
  └─ Re-schedules next BGAppRefreshTask

Entitlements required ​

xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
  <string>io.edgecoder.ios.runtime</string>
  <string>io.edgecoder.ios.heartbeat</string>
</array>
<key>com.apple.developer.bluetooth-always-authorization</key>
<true/>

Power-aware scheduling ​

SignalMeaning
onExternalPowerCharging / external power connected
batteryLevelPctBattery percentage (0–100)
lowPowerModeiOS Low Power Mode active

These are reported to the coordinator in every heartbeat's powerTelemetry payload. The runOnlyWhileCharging toggle (persisted in UserDefaults) gates the start() call: if true and not on external power, the runtime refuses to start with a status message.

Assignment decision ​

Bluetooth Local transport ​

When in Bluetooth Local mode, BluetoothTransport makes the iPhone act as a CBPeripheralManager (BLE server):

BLE service layout ​

UUID suffixCharacteristicPropertiesRole
...0002requestCharwrite / writeWithoutResponseMac sends inference request JSON
...0003responseCharnotify / readPhone sends inference result back
...0004statusCharnotify / readPhone pushes modelState + batteryPct

Service UUID: EC0D-0001-EC0D-EC0D-EC0D-EC0D0001EC0D

Wire protocol (JSON over BLE) ​

Request (Mac → Phone, written to requestChar):

json
{ "id": "<uuid>", "prompt": "<text>", "maxTokens": 512 }

Response (Phone → Mac, notified on responseChar):

json
{ "id": "<uuid>", "output": "<text>", "ok": true, "durationMs": 450 }

Status (Phone → Mac, notified on statusChar):

json
{ "modelState": "ready", "batteryPct": 87 }

Responses larger than 512 bytes are chunked and sent sequentially with 10ms pauses between chunks.

Mac-side integration ​

The Mac side uses edgecoder-ble-proxy — a Swift CBCentralManager companion binary that:

  1. Scans for peripherals advertising the EdgeCoder BLE service UUID.
  2. Connects, subscribes to responseChar and statusChar.
  3. Exposes a local HTTP server on 127.0.0.1:11435:
    • GET /status → BLE connection state, device name, battery, model state.
    • POST /api/generate → { prompt, maxTokens } → forwards over BLE, returns result.

The IDE provider server (port 4304) starts this binary automatically and registers its status URL with IntelligentRouter so the routing waterfall (bluetooth-local → ollama-local → swarm → edgecoder-local) works transparently.

Building the proxy:

bash
npm run build:ble-proxy
# Compiles src/bluetooth/swift-ble-proxy/ and installs to /opt/edgecoder/bin/

Manual test:

bash
/opt/edgecoder/bin/edgecoder-ble-proxy --port 11435 &
curl http://127.0.0.1:11435/status

IDE task tracking (iOS app) ​

When the phone is in Bluetooth Local mode and a Mac IDE submits a task, it appears in the IDE tab of the iOS app in real-time:

  • Running — spinner while inference runs on the phone
  • Success — green check, output viewable in detail sheet
  • Failed — red cross with error

The IDE tab badge shows the count of in-progress tasks. Tapping any entry opens a full detail view with prompt, generated output, duration, and timestamp.

Operator practices ​

  • Keep iOS fleet policy conservative by default (low battery threshold, runOnlyWhileCharging for intensive models).
  • Track assignment drop reasons to understand power-policy impact.
  • Use separate worker pools for mobile vs desktop if reliability goals diverge.
  • Bluetooth Local mode is unrewarded — communicate this clearly to users.
  • The edgecoder-ble-proxy binary requires Bluetooth access via System Settings → Privacy & Security → Bluetooth on the Mac.