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.

The session SDK decouples the engine from data sources via two abstractions. This page covers the contracts, the bundled implementations, and the integration semantics with the engine.

BiosignalProvider

The HR/HRV/RR source. Stream-based: the engine subscribes once per session and unsubscribes on stop.

Contract (Dart)

abstract class BiosignalProvider {
  bool get isAvailable;
  String get name;
  Stream<BiosignalSample> startStreaming();
  void stopStreaming();
}
Kotlin (interface BiosignalProvider) and Swift (protocol BiosignalProvider) mirror this. name is a debug identifier (not displayed to users). isAvailable is a self-report — the engine still subscribes when false if the host explicitly opts in.

BiosignalSample

class BiosignalSample {
  final int timestampMs;
  final double bpm;
  final double? rrIntervalMs;
  final ({double x, double y, double z})? accelerometer;  // record type
  final double? spo2;
}
Required: timestampMs (epoch ms), bpm (instantaneous heart rate). Optional: rrIntervalMs (single inter-beat interval), accelerometer (tri-axis, present on watch builds), spo2 (when the wearable reports it).

Engine consumption

LiveSessionEngine subscribes on startSession and pushes each sample into a fixed-capacity ring buffer sized at windowSec. There is no provider-side rate limiting; the engine drops oldest samples on overflow. The accelerometer and SpO₂ fields are not consumed by LiveSessionEngine today — they are forwarded only when the engine emits BiosignalFrame events (raw streaming opt-in). The watch companions populate them at source.

Bundled implementations

ImplementationPackageNotes
MockBiosignalProviderall threeDeterministic with seed; safe for CI.
WatchBiosignalProviderDartBridges through SessionChannel. isAvailable = true optimistically.
WearBiosignalProviderKotlinReads from synheart-wear adapters.
HealthConnectBiosignalProviderKotlinReads from Health Connect on the phone.
HealthKit providerSwiftUnder SynheartSessionHealthKit.
Wear (watchOS) providerSwiftUnder SynheartSessionWear.
External providers (host-supplied):
  • BLE HRM: hosts can build a BiosignalProvider over synheart-wear’s BLE HRM client.
  • Custom mocks for replay tests.

BehaviorProvider

The behavioral signal source. Pull-based: the engine calls currentSnapshot() at each frame tick.

Contract

abstract class BehaviorProvider {
  bool get isAvailable;
  String get name;
  BehaviorSnapshot? currentSnapshot();
}
A null return means “no snapshot available right now”; the engine omits the behavior field on that frame.

BehaviorSnapshot

class BehaviorSnapshot {
  final int timestamp;            // epoch ms when captured

  // typing
  final double? typingCadence;
  final double? interKeyLatency;
  final int? burstLength;

  // scrolling
  final double? scrollVelocity;
  final double? scrollAcceleration;
  final double? scrollJitter;

  // taps and app context
  final double? tapRate;
  final int appSwitchesPerMinute;          // default 0, not nullable
  final double? foregroundDuration;
  final double? idleGapSeconds;

  // session-level indices
  final double? stabilityIndex;
  final double? fragmentationIndex;
}
The shape is field-for-field aligned with synheart_behavior’s BehaviorStats so the two SDKs share a contract without import dependencies. JSON serialisation uses snake_case. When emitted on a SessionFrame.behavior field, only the non-null fields appear (the toJson strips nulls except appSwitchesPerMinute and timestamp).

Bundled implementations

ImplementationPackageNotes
MockBehaviorProviderall threeReturns stable mid-range values for tests.
synheart_behavior-backed providerDart, Kotlin, SwiftApps wire BehaviorProvider to a synheart_behavior BehaviorEngine; the snapshots come from the active behavior session.
The session SDK does not bundle a default real-data behavior provider; that wiring lives in synheart-core (which depends on both packages).

Integration with the engine

SessionConfig
  ├── biosignalProvider → engine subscribes for the duration of the session
  └── behaviorProvider  → engine pulls a snapshot at each emit_interval_sec
                           (and once more at SessionSummary)
Both providers are optional. With neither configured:
  • LiveSessionEngine still emits SessionStarted, frames with sample_count = 0, hr_mean_bpm = 0.0, no behavior, and a final SessionSummary.
  • This is the deterministic-no-data path useful for testing the framing logic itself.

Provider state machine (informal)

provider constructed
    │ isAvailable = false?  → engine skips subscription entirely

startStreaming() called


streaming
    │ stopStreaming() called  (engine on session end / dispose)

stopped
Providers must tolerate stopStreaming() being called multiple times. They must stop emitting after stopStreaming() returns; samples emitted after the engine has unsubscribed are dropped silently.

Watch provider specifics

WatchBiosignalProvider:
  • BiosignalFrame events → emit each BiosignalSample directly to the controller.
  • SessionFrame events → synthesise one sample from metrics.hr_mean_bpm with rrIntervalMs = 60000.0 / bpm.
  • SessionStarted, SessionSummary, SessionError → ignored (lifecycle, not biosignal data).
Channel errors are non-fatal: the provider swallows them so the engine doesn’t terminate the session on watch reconnect/disconnect.

Authoring a provider

A custom BiosignalProvider should:
  • Allocate any sensor resources (BLE GATT subscribe, HK authorization, etc.) on startStreaming().
  • Release them on stopStreaming().
  • Emit samples on the broadcast Stream<BiosignalSample> returned from startStreaming().
  • Not block the calling thread — the engine subscribes synchronously.
A custom BehaviorProvider should:
  • Maintain its own internal accumulators.
  • Return a fresh BehaviorSnapshot on each currentSnapshot() call (or null when no recent activity).
  • Use real DateTime.now().millisecondsSinceEpoch for the snapshot timestamp.
  • Lifecycle — when the engine subscribes/unsubscribes.
  • Watch ProtocolBiosignalBatch and BiosignalSample on the wire.