Skip to content

feat(tools): eight_sleep tool (unofficial cloud API)#6471

Open
theonlyhennygod wants to merge 1 commit intomasterfrom
feat/eight-sleep-tool
Open

feat(tools): eight_sleep tool (unofficial cloud API)#6471
theonlyhennygod wants to merge 1 commit intomasterfrom
feat/eight-sleep-tool

Conversation

@theonlyhennygod
Copy link
Copy Markdown
Collaborator

Summary

  • Base branch: master
  • What changed and why:
    • Adds an eight_sleep Tool that drives an 8Sleep Pod via 8Sleep's unofficial cloud API. The integration was previously a placeholder; this gives operators agent-driven temperature control and read-only access to bed state and sleep metrics.
    • Auth: POST {email, password}<api_base_url>/login, cache session.token in memory (no on-disk persistence), retry once with a fresh token on 401. Token sent via Session-Token header per pyEight conventions.
    • v1 surface (intentionally tight): get_bed_state, get_metrics, set_temperature (-100..=100 per side). set_temperature is gated by ToolOperation::Act AND a configurable allowed_sides allowlist enforced server-side in execute().
    • Wires into the schema-driven registry via #[integration(category = "ToolsAutomation", display_name = "8Sleep", description = "Smart mattress (unofficial API)", status_field = "enabled")] on EightSleepConfig.
  • Scope boundary:
  • Blast radius: zeroclaw-config (additive [eight_sleep] block, default disabled), zeroclaw-tools (new module + tokio Mutex for in-memory token cache), zeroclaw-runtime/src/tools/mod.rs (config-gated registration + tool re-export), zeroclaw-runtime/src/integrations/mod.rs (setup-hint arm), docs.
  • Linked issue(s): Closes #6450. Sibling tracking issues: [Feature]: Home Assistant integration tool #6448 (HA, in PR feat(tools): home_assistant tool for HA REST API #6464), [Feature]: Philips Hue integration tool #6449 (Hue, in PR feat(tools): philips_hue tool for local Hue Bridge #6470).

Validation Evidence (required)

cargo fmt --all -- --check
cargo clippy -p zeroclaw-config -p zeroclaw-tools -p zeroclaw-runtime --all-targets -- -D warnings
cargo test -p zeroclaw-config -p zeroclaw-tools -p zeroclaw-runtime
  • Commands run and tail output:
    • cargo fmt --all -- --check → clean (no diff after cargo fmt --all).
    • cargo clippy ...Finished dev profile [unoptimized + debuginfo] target(s) in 9.77s. No warnings.
    • cargo test -p zeroclaw-config -p zeroclaw-tools -p zeroclaw-runtime
      • test result: ok. 620 passed; 0 failed; 0 ignored (zeroclaw-config)
      • test result: ok. 1630 passed; 0 failed; 1 ignored (zeroclaw-tools)
      • test result: ok. 1162 passed; 0 failed; 0 ignored (zeroclaw-runtime)
      • Total: 3,413 passed, 0 failed.
    • New tool: 18 tests under eight_sleep::tests::* — name, description (asserts the unofficial-API warning is present), schema shape (action enum + level bounds), URL/base normalization, side-allowlist trimming and case-folding, build_temperature_body shape (correct per-side fields, no leakage to the other side, negative-level handling), missing-action / unknown-action / missing-side / invalid-side / missing-level / out-of-range / disallowed-side / empty-allowlist paths, spec() reflection.
  • Beyond CI — what did you manually verify?
    • Schema-driven registry surfaces 8Sleep automatically — confirmed by reading the new all_integrations(&Config) loop, which consumes Config::integration_descriptors() (auto-generated from the #[integration(...)] attribute on EightSleepConfig).
    • zeroclaw integrations info "8Sleep" prints the [eight_sleep] config example with the unofficial-API disclaimer (new arm in show_integration_info).
    • Not verified live: I did not authenticate against a real account or send any real set_temperature requests. Unit tests cover gating logic exhaustively (allowlist, side validation, level bounds, security policy) and request-body assembly (build_temperature_body), but the wire format on the actual cloud endpoint is verified only against pyEight conventions, not a live cluster.
  • If any command was intentionally skipped, why: Skipped ./dev/ci.sh all for the same reason as feat(tools): home_assistant tool for HA REST API #6464 / feat(tools): philips_hue tool for local Hue Bridge #6470: change is scoped to three crates + docs + a changelog entry; targeted clippy+test runs already gated those crates with -D warnings.

Security & Privacy Impact (required)

  • New permissions, capabilities, or file system access scope? No. HTTP only.
  • New external network calls? Yes. The tool talks to https://client-api.8slp.net/v1 (or whatever api_base_url resolves to). Disabled by default; operator opts in.
  • Secrets / tokens / credentials handling changed? Yes. New email/password fields on EightSleepConfig. password carries #[secret] (encrypted at rest when [secrets] encrypt = true) and falls back to EIGHT_SLEEP_PASSWORD env var. The minted session token is held in tokio::sync::Mutex<Option<SessionToken>> and never written to disk.
  • PII, real identities, or personal data in diff, tests, fixtures, or docs? No. Tests use user@example.invalid, neutral synthetic credentials, and https://example.invalid/v1 for URL normalization.
  • Risk and mitigation:
    • 8Sleep credentials are higher-stakes than typical API tokens — they unlock the user's full 8Sleep account (sleep history, payment, app settings). Mitigation: #[secret] storage, env-var fallback so the password never has to land in a config file, in-memory-only token cache, and an allowed_sides allowlist that lets operators throttle the agent to one side of a shared bed.
    • The 401 retry path can hammer the upstream if credentials are wrong (re-login on every request). Mitigation: documented in the troubleshooting section so operators know to disable on auth failure.

Compatibility (required)

  • Backward compatible? Yes. Additive config, default enabled: false. Config::default() includes eight_sleep: EightSleepConfig::default().
  • Config / env / CLI surface changed? Yes — new [eight_sleep] block; new EIGHT_SLEEP_PASSWORD env var fallback. No CLI changes.
  • Upgrade steps for existing users: None required. To opt in, add [eight_sleep] enabled = true, email, and either password or EIGHT_SLEEP_PASSWORD. Optionally narrow allowed_sides.

Rollback (required for risk: medium and risk: high)

  • Fast rollback command/path: Set [eight_sleep] enabled = false in ~/.zeroclaw/config.toml (or remove the block) and restart the agent. The tool is unregistered on next boot.
  • Feature flags or config toggles: eight_sleep.enabled (master switch). eight_sleep.allowed_sides (per-mutation throttle; empty disables all set_temperature calls while keeping reads). eight_sleep.api_base_url (in case 8Sleep moves endpoints — operator can pin without a code change).
  • Observable failure symptoms:
    • Logs: eight_sleep: enabled but no password found ... or ... email is empty — skipping registration indicate config issues.
    • Logs: 8Sleep login failed (401) indicates wrong creds or a Cognito-mandated account.
    • Logs: 8Sleep {VERB} {path} failed (4xx/5xx) indicates upstream API issues — most likely 8Sleep changed an endpoint and the integration needs a follow-up.
    • Tools registry at /api/tools will not include eight_sleep if email or password is missing.

Adds an `eight_sleep` Tool that drives an 8Sleep Pod via 8Sleep's
unofficial cloud API, following the auth and endpoint conventions of
the open-source pyEight library. v1 supports `get_bed_state`,
`get_metrics`, and per-side `set_temperature` (-100 cools, 0 holds,
+100 warms). Alarm scheduling and prime-cycle control are deferred
to follow-ups: alarms have a wider API surface and higher safety
stakes (a wrong wake time is bad UX), and prime endpoints differ
between Pod generations.

`EightSleepConfig` is disabled by default and carries
`#[integration(category = "ToolsAutomation", display_name = "8Sleep",
...)]` so the schema-driven registry surfaces it automatically. The
`password` field is `#[secret]`-encrypted at rest and falls back to
`EIGHT_SLEEP_PASSWORD`. The session token is cached in memory only
and refreshed on `401`. `set_temperature` is gated by the
`allowed_sides` allowlist (defaults to `["left", "right"]`); empty
allowlist blocks all mutations while leaving reads available.

Setup-guide doc carries an explicit "unofficial API" disclaimer and
documents the Cognito-account limitation (newer accounts on the
mandatory OAuth flow are not supported in v1).

Closes #6450
@github-actions github-actions Bot added the docs Auto scope: docs/markdown/template files changed. label May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Auto scope: docs/markdown/template files changed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant