MalloryEstate Briefing
M·036Designed
← All briefings

Awareness

Device Signals

Automatic mode detection from phone motion, geofences, and DND — privacy-first by construction: raw location and motion never leave the device, only a coarse hint.

Dated 2026-06-21Programme Awareness

Situation

Modes v1 made Mallory context-aware, but the active mode only ever came from a manual override, a Hub calendar hint, or a static routine table — nothing caught a commute starting or an arrival at the office automatically. Device signals add that: the phone's own sensors (Activity Recognition, geofences, Do-Not-Disturb) become a third hint source feeding the same resolver the Modes v1 spec deliberately left extensible.

The core discipline is privacy-first, coarse-on-device: this is a hint, never an override — an explicit manual mode always wins, and the device can only ever propose. Every sensor is independently opt-in and off by default, and the design's own privacy invariants (raw coordinates never leave the device, geofence coordinates travel web→native over the bridge only, edge payloads are validated to closed enums server-side) are treated as first-class requirements, not implementation details.

Key decisions

Signal scope (v1)

Motion (Activity Recognition), DND state, and on-device geofences (home/office) — geofences are included so the device can also propose a *hat*, at the cost of a background-location grant, though raw coordinates still never leave the phone.

Per-axis hint precedence

The device leads the activity axis (ground truth on movement); the calendar leads the hat axis (knows which account a meeting belongs to); each can fill the other axis as a lower-priority fallback, and the two hints live in separate KV keys so they never clobber each other.

Geofence setup via map picker

A Leaflet/OSM web picker in /settings, reached through the existing MalloryNative bridge — coordinates go web→native into prefs only; only the derived label ever reaches the edge.

Per-signal consent

Independent toggles for Motion, Location, and DND, each off by default, each requesting its own OS permission only on enable — motion-only is a valid state that never grants background location.

DND's off is weak

The device never sends a strong off from DND alone, since a focus block looks identical to knocking off early — marking it weak lets the resolver rank it below a known calendar meeting without needing a confidence field.

Architecture

Split across two implementation plans with a shared contract: the edge core (per-axis resolver, KV, CN-gated route) is additive and deployable-while-dormant, built first so the Android/web client has a real contract to post against.

src/lib/mode.ts — ModeDevice + per-axis resolveMode (edge)
A new optional 4th device param; with it undefined the function is byte-identical to today, so existing call sites and tests are untouched.
src/routes/mode.ts — POST /mode/device (edge)
CN-gated to DEVICE_SIGNAL_CN (the Android shell's Access service-token Client ID), validates closed enums, an empty body {} clears device influence.
mode:device KV key (edge)
Separate from mode:hint so device and calendar never overwrite each other; TTL expiry is the backstop for a dead or quiet phone.
DeviceSignalReceiver + DeviceSignalManager (Android client)
Event-driven BroadcastReceiver for Activity Recognition and geofence transitions, plus DND read via the existing NotificationListenerService — no continuous GPS/motion stream, hysteresis before flipping state.
Prefs + MalloryNative bridge (Android client)
Per-signal enabled flags and geofence definitions stored on-device only; new bridge methods (getDeviceSignalConfig, setSignalEnabled, setGeofences) carry config web→native.
GeofencePicker (web /settings, client)
Leaflet/OSM map component for dropping Home/Office pins with an optional hat and workplace flag.

Risks & guardrails

Privacy is the core constraint, not a footnote

Seven explicit invariants gate the design — e.g. raw coordinates never leave the device, mode:device writes are CN-gated to the phone's service token alone, and toggling a signal off posts an empty state immediately to stop emission.

DND ambiguity

A daytime focus block and genuinely knocking off early look identical to DND alone, so the weak-off ranking is load-bearing to avoid the device overriding a known meeting.

Stale or lost device

Mitigated by TTL self-expiry on every device assertion — a quiet, lost, or stolen phone's stale state decays automatically rather than sticking.

Two plans, two toolchains

The edge plan (Vitest-tested, additive, deployable-while-dormant) and the client plan (Kotlin/gradle + on-device adb verification, not unit-TDD-able) must build in order — edge first so the contract is real — even though the client doesn't import edge code and can develop in parallel once the contract is fixed.

Delivery

Edge plan — resolver + route

Per-axis resolveMode plus the ModeDevice type, mode:device KV wiring, and the CN-gated POST /mode/device route; verified by a full Vitest suite plus a type-check pass. Deployable while dormant — with no DEVICE_SIGNAL_CN set and no phone posting, behaviour is unchanged.

Client plan — Android native

Gradle dependency + manifest permissions/receiver, Prefs additions, the DeviceSignals derivation object, DeviceSignalReceiver + DND hook, and MainActivity bridge methods and permission launchers.

Client plan — web settings

A pure deviceSignals.ts lib (unit-tested), the Leaflet GeofencePicker component, and the device-signals section wired into /settings.

On-device verification

adb-driven end-to-end checks on a real handset, since sensor transitions can't be unit-tested headlessly.

Deploy held for Alex

Both plans are PR-only; deployment and the on-device Android build are explicitly held for the operator.

Out of scope

Learned/automatic place detection (frequent-location clustering)Activity states beyond commuting/working/offThe MK office geofence (config-present; lights up when the second Google account connects, no code change)A "currently detected" indicator in the StatusSheet (candidate fast-follow)Self-hosted map tiles (v1 uses public OSM tiles)The entire Notification Intelligence track (typed events, triggers, memory-from-notifications, acting — a separate future brainstorm)
spec · docs/superpowers/specs/2026-06-21-device-signals-design.md
plan · docs/superpowers/plans/2026-06-21-device-signals-client.md
plan · docs/superpowers/plans/2026-06-21-device-signals-edge.md
Mallory · Estate briefing