Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.synheart.ai/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Synheart Core Edge SDK is a watch-class build of Synheart Core targeted at Apple Watch, Wear OS, and other low-power wearables. It runs the session lifecycle (start / pause / resume / stop), captures HR + motion samples, and either streams the raw samples to a paired phone for HSI computation, or — when the native runtime binary is bundled — runs the edge HSI pipeline locally. Two language flavours, same API shape:
  • SynheartCoreEdge (Swift) — for watchOS / iOS / macOS targets
  • ai.synheart:synheart-core-edge (Kotlin) — for Wear OS / Android targets
Both consume the same native runtime (built with the edge feature) via FFI when present. When absent, both fall back to a STREAM mode that surfaces raw samples for the host to relay.

When to use Edge vs Core

Synheart Core (kotlin / swift)Synheart Core Edge
TargetPhone / desktopWatch / wearable
Runtime variantstable (with ONNX) or labedge (no ONNX, deterministic models only)
Binary sizefull~9 MB armv7, ~13 MB arm64
Default biosignal sourceplatform-specific health adapterplatform-specific health adapter (overridable)
Compute modealways compute locallySTREAM (default) or COMPUTE_LOCAL
Watches almost always run STREAM in production — the phone has more thermal headroom and a longer-running runtime. COMPUTE_LOCAL is for standalone-watch use cases (no phone reachable) and offline sessions.

Installation

Swift (SwiftPM)

Package.swift:
.package(url: "https://github.com/synheart-ai/synheart-core-swift-edge.git", from: "0.0.3"),
Target dependency:
.product(name: "SynheartCoreEdge", package: "synheart-core-swift-edge"),
Platforms: watchOS 9.0+, iOS 15.0+, macOS 13.0+.

Kotlin (Gradle)

Until the SDK publishes to Maven Central, consume via composite build:
// settings.gradle.kts
val coreEdge = providers.environmentVariable("SYNHEART_CORE_EDGE_LOCAL").orNull
if (!coreEdge.isNullOrBlank()) {
    includeBuild(coreEdge)
}
Then in the wear module:
dependencies {
    implementation("ai.synheart:synheart-core-edge")
    implementation("ai.synheart:synheart-session:0.1.0")
}
Targets: Wear OS 4 / API 33+.

Quickstart

Swift (watchOS)

import SwiftUI
import SynheartCoreEdge

@main
struct MyWatchApp: App {
    @StateObject private var engine = WatchSessionEngine(
        outbox: EdgeOutbox(),
        sessionManager: EdgeSessionManager()
    )

    var body: some Scene {
        WindowGroup {
            VStack {
                Text("HR: \(Int(engine.currentHr))")
                Button("Start") {
                    engine.startSession(config: .focusDefault())
                }
            }
        }
    }
}
HealthKitBiosignalProvider is constructed automatically using SynheartWear under the hood. Inject your own to override:
let bleProvider = MyPolarChestStrapProvider()
let engine = WatchSessionEngine(provider: bleProvider, ...)

Kotlin (Wear OS)

class WatchApp : Application() {
    private val provider by lazy { HealthServicesBiosignalProvider(this) }
    private val motionSensor = MotionSensor()

    val engine: WatchSessionEngine by lazy {
        WatchSessionEngine(
            provider = provider,
            motionSensor = motionSensor,
            outbox = EdgeOutbox(this),
            sessionManager = EdgeSessionManager(this),
        )
    }
}
The Wear OS adapter (HealthServicesBiosignalProvider) wraps Wear OS Health Services MeasureClient. It lives in the host app rather than the SDK because Wear platform binding is the host’s choice.

Engine API (both platforms)

startSession(config, mode? = nil)   // mode resolves: COMPUTE_LOCAL if runtime present else STREAM
pauseSession()
resumeSession()
stopSession()
startEdgeSession(preset)             // standalone watch session (no phone)
acknowledgeArtifacts(ids)            // after phone confirms it received them
Observable state:
  • currentHr: Double — latest HR sample (BPM)
  • state: WatchSessionState.idle | .starting | .running | .paused | .stopping | .syncing | .error
  • elapsedSec: Int, remainingSec: Int, pendingArtifacts: Int
  • lastMetrics: [String: Any]? — frame metrics (hr_mean_bpm, hr_sdnn_ms, rmssd_ms, motion_rms_g)
Event/sample streams:
  • Kotlin: engine.hrEvents (Flow), engine.events (Flow), engine.bioSamples (Flow, STREAM only)
  • Swift: engine.onHrSample, engine.onEvent, engine.onBiosignalSample (callback assignments)

Mode resolution

When startSession(...) is called with mode = nil (the default), the engine probes for the native runtime via the platform’s standard dynamic-symbol lookup:
  • Runtime resolves → mode = COMPUTE_LOCAL, HSI computed on-watch, artifacts go to EdgeOutbox
  • Runtime missing → mode = STREAM, raw samples surfaced via bioSamples / onBiosignalSample for the host to relay to the phone
Pass an explicit mode to force one path (e.g. .stream for low-battery preference, .computeLocal for standalone use).

Runtime native binary

Android (Wear OS)

libsynheart_core_runtime.so per ABI lives at wear/src/main/jniLibs/<abi>/:
synheart install runtime --platform android --variant edge
Or, building from source against a local runtime checkout:
SYNHEART_CORE_RUNTIME_LOCAL=/path/to/synheart-core-runtime \
  ./gradlew :wear:assembleDebug
(The reference wear app includes a :wear:vendorEdgeRuntime Gradle task that builds the edge runtime and drops the .so into jniLibs/. Pixel Watch 1 is 32-bit only — armeabi-v7a is sufficient.)

Apple (iOS + watchOS)

synheart install runtime --platform ios --variant edge
Lands two xcframeworks side-by-side under synheart/vendor/runtime/ios/:
SynheartCoreRuntime.xcframeworkiOS device + simulator (dynamic Mach-O frameworks, dSYMs embedded for App Store Connect symbolication)
SynheartCoreRuntime-watchos.xcframeworkwatchos-arm64 + watchos-arm64-simulator (static .a archives)
Xcode picks the right slice per target automatically — the iOS app links the dynamic framework, the watchOS app links the static archive. The SDK’s RuntimeBridge uses dlsym(RTLD_DEFAULT, …), so the watch’s statically-linked symbols resolve the same way as the iOS dylib’s dynamically-loaded ones. Why two: Apple’s xcframework format prohibits mixing -framework and -library inputs in a single bundle, and the native runtime’s tier-3 watchOS target only emits a static library (not a dynamic one). Building from source (runtime contributors): make ios-edge produces both. See the runtime repo for toolchain prerequisites.

Session lifecycle (RFC §8.1)

        IDLE
         │ start

      STARTING ──error──┐
         │              │
         │ ▶            ▼
       RUNNING ◀──pause──┐    ERROR
         │              ▲     │
       resume           │     │
         ▼              ▼     │
       PAUSED ──stop──► STOPPING ──► IDLE

                              └──► SYNCING ──► IDLE
pause is host-driven (UI interruption, call, breath). Resume shifts startedAtMs forward by the paused interval so elapsed-time accounting skips the pause.

Compute provenance

Every HSI artifact the engine produces (in COMPUTE_LOCAL mode) is stamped with a meta.synheart.compute block per the edge-tiering RFC:
"meta": {
  "synheart": {
    "compute": {
      "tier": "edge_deterministic",
      "runtime": "synheart_core_edge",
      "runtime_version": "0.12.6",
      "model_id": "rulepack://hsi-edge/v1.4",
      "computed_on": "wearable",
      "session_role": "canonical"
    }
  }
}
Cloud-side tier-aware queries (overview KPI breakdowns, shadow reconciliation) join on this block.

See also