Skip to content

tiny

Agent tools — the agent reaches the ring·

How the agent reaches the ring: the ring_commands queue the phone gateway claims, the nine verbs, and the health_* tools.

  • tiny users and agent builders
  • 1 min read
  • 248 words

In 10 seconds

"tiny, find my ring" cannot open a socket to the ring — it enqueues a ring command in the worker, the phone gateway claims it over its BLE link, executes, posts the result; the ring_command tool polls ≤ 90 s. Nine verbs: find · battery · status · measure_hr · measure_spo2 · set_auto_hr · sync · memo_pull · record. First metal proof 2026-09-19 20:35Z: iPhone claimed memo_pull in 6 s.

agent / web ──POST /api/health/ring/commands──▶ worker (pending) ◀──POST …/claim (device token)── phone gateway
agent ◀── GET …/commands/:id (poll ≤ 90 s) ──── worker (done)   ◀──POST …/:id/result ──────── phone gateway ⇄ ring (BLE)
The nine verbs — args, BLE sequence, result shape (the contract a gateway implements)
cmd args (validated server-side) BLE sequence (decimal cmds) result the gateway posts
find mode 1..3 (default 1) 4 vibrateLED(mode) → echo {acked:true}
battery 6 → pct, charging {battery, charging}
status 37, 6, 0x2F audio state, 0x3D file count, 55 features {firmware, mac, battery, charging, audio_state, memo_count, features}
measure_hr seconds 5..120 (default 30) 7 open → collect 11 ticks for seconds → 8 close (also ingest samples) {hr:[{ts,bpm}], avg, n}n:0 ⇒ ring off finger
measure_spo2 23 01 → wait 24 (or 0x19 ended-without-value) → 23 00 {spo2} or {spo2:null, reason:"no reading"}
set_auto_hr enabled bool, interval_min 1..60 (default 5) 30 00 00 17 3B <en> <min> → echo {enabled, interval_min}
sync days 1..7 (default 2) 33/34/36 per day → 42/43/45; ingest via /api/health/ingest {samples, days}
memo_pull 0x2F (skip if recording) → 0x3D count → 0x40, 1 s, 0x34 stream → 0x36 echo; POST /api/health/media {memos:[{id, duration_s}], remaining}
record seconds (default 30) 0x30 mode 1 → 0x32/0x33 live stream (on the charger the ring records to flash instead → memo pull) {media_id, duration_s, transcript}

Never queued, never sent: 39 reboot / factory reset, 67 unbind, SUOTA FEF5 (ring-cli raw refuses 39|67 too). Expiry: anything not done 10 minutes after creation flips to expired — a stale vibrate never fires an hour later.

Tools the agent has·

tool does
ring_command {device_id, cmd, args?, wait_s ≤ 90} enqueue + poll; note explains a pending/expired command so the model says "the phone hasn't reached the ring yet" instead of inventing a result; action: get | list
health_summary · health_series · health_query the day, a metric series, ad-hoc questions — cradle readings excluded
health_devices · health_memos · health_notes rings with labels/battery/last sync · memos + transcripts (list/transcribe/delete) · notes
health_alert_rules {memo_agent, button_prompt, auto_hr_min, …} the owner's switches: memo → agent, tap → hotkey, auto-HR interval (rides on every claim reply)

Prompt routing: "find my ring" → find · "measure my heart rate now" → measure_hr · "how much battery" → battery · "pull my memos" → memo_pull · "record a 30 second voice note" → record.

Doors (HTTP) — owner session and gateway token

Owner door (session; the app proxies with ?userId + X-Internal-Key):

  • POST /api/health/ring/commands {device_id, cmd, args?, requested_by?}201 {command}. device_id = health_devices.id (hd_…). Max 8 waiting per ring (429 over_limit). Unknown cmd / bad args → 400 with the reason sentence.
  • GET /api/health/ring/commands?device_id=&status=&limit={commands} newest first.
  • GET /api/health/ring/commands/:id{command}; status pending | claimed | done | failed | expired.

Gateway door (device token — the fleet device's {deviceId, token}, same no-oracle rule as /api/health/ingest):

  • POST /api/health/ring/commands/claim {deviceId, token, device_ids?: ["hd_…"]}{commands, poll_after_ms} — every pending command for the user's rings (optionally only the rings this phone holds) becomes claimed by this device; re-claiming returns the same rows (idempotent). Poll every poll_after_ms (2 s while busy, 15 s idle); on iOS piggy-back on the background BLE wake-ups.
  • POST /api/health/ring/commands/:id/result {deviceId, token, ok, result?, error?}{command}; a second post is a no-op duplicate:true. Emits event ring_command for the owner's feed.

Gateway status (2026-09-19)·

gateway claim loop find battery/status measure_hr/spo2 set_auto_hr sync memo_pull record
iOS RingGateway OTA 106 ✅ 15 s / 2 s busy ✅ on metal 🟡 stream off-charger unseen
Android RingGateway ✅ built ✅ — all pending install on the Pixel
Mac ring-cli (reference) find audio-pull audio-record