Skip to content

v2.4.0 - Pluggable Weather, Process Supervisor & Proactive Scheduling

Choose a tag to compare

@pjdoland pjdoland released this 09 May 17:12
· 60 commits to main since this release

New Features

  • Pluggable weather/context provider: Refactors the hardwired wttr.in fetch into a WeatherProvider interface with three adapters — wttr.in (default; free, no API key), Home Assistant (local, no third-party egress), and manual (offline / testing, zero network). Auto-selects when WEATHER_PROVIDER is unset; existing ZIPCODE-only setups keep working unchanged. Set WEATHER_PROVIDER=none to disable weather entirely.
  • Process supervisor for unattended deployments: New scripts/supervisor.py wraps python -m jf_sebastian.main with exponential-backoff restart, watchdog kill of hung children via heartbeat-file staleness, enriched crash reports, and permanent-failure detection (CRITICAL log + extended backoff after N consecutive crashes). Includes launchd plist (macOS) and systemd unit (Linux) templates. Designed for museum exhibits, eldercare companions, kids' rooms — anywhere the toy needs to keep running across PortAudio/RVC crashes.
  • Proactive scheduler / ambient mode: Personalities can now define proactive utterances in personalities/<name>/scheduled_events.yaml — morning greetings, bedtime stories, holiday surprises, scheduled reminders. Tiny schedule syntax (HH:MM, HH:MM weekdays, HH:MM YYYY-MM-DD); each event uses say: (verbatim TTS) or prompt: (LLM in character). Events only fire when state is IDLE — never interrupts an in-progress conversation. Quiet-hours suppression with load-time warnings.
  • ROADMAP.md: Public roadmap synthesized from a seven-persona peer-reviewed codebase audit. Documents Tier 1-3 prioritization plus excluded items, with 70 enhancement suggestions evaluated by Borda count.

Bug Fixes

  • Fix urlsplit raising ValueError on a malformed HOME_ASSISTANT_URL and breaking the entire weather pipeline (including auto-fallback to wttr)
  • Fix _refresh_in_flight flag leaking True when the weather provider became unconfigured mid-refresh, suppressing all future refreshes
  • Fix Whisper weather cold-miss (first-conversation timeout caused "I can't check the weather" responses)
  • Fix numpy int16 overflow RuntimeWarning in RMS amplitude calculation
  • Fix race condition in scheduled-event callback that could re-init PyAudio after audio_player.cleanup() during shutdown
  • Fix wake-word detector race during scheduled-event TTS synthesis (added atomic try_transition CAS to state machine)
  • Fix _pause_wake_for_playback flag set even when pause() raised, causing missed resume

Improvements

  • Local-first weather security: Bearer token to Home Assistant is refused over plain HTTP to non-private hosts (loopback, RFC1918, link-local, *.local allowed); URL-quoted entity ID; allow_redirects=False to prevent token leak via cross-host redirects; defensive .get()-chain parsing for wttr; Celsius detection tolerates "C", "celsius", etc.
  • Defense-in-depth supervision: launchd/systemd watch the supervisor; supervisor watches the child; heartbeat thread (utils/heartbeat.py) reports liveness so even hung-PROCESSING is detected and killed via os.killpg on the child's process group (kills ffmpeg / RVC subprocesses, not just the immediate child)
  • Crash report enrichment: PID, personality, ran_for, heartbeat age at exit, hostname, Python version, last 100 log lines; pruned to most recent N (default 200) so disk can't fill on a permanent-failure loop
  • Rotating logs: jf_sebastian.log and supervisor.log use RotatingFileHandler (10MB × 5) so unattended deployments can't fill disk
  • Atomic state transitions: New StateMachine.try_transition(expected, target, trigger) compare-and-swap closes TOCTOU windows for any caller that needs to gate on current state; try_transition and transition_to share a single body via _apply_transition_locked
  • Cooperative shutdown: SIGTERM/SIGINT use cooperative _running = False instead of sys.exit() from a signal handler; SystemExit(0) from handler when SIGTERM arrives during __init__ so the supervisor doesn't have to escalate to SIGKILL
  • Hobbyist-friendly schedule UX: weekdays / weekends aliases, friendlier error messages for typos ("empty weekday token" instead of cryptic unknown weekday ''), past-dated one-shot warnings at load, items listed in scheduler startup log so users can verify their event loaded
  • Updated wake word models for Fred, Johnny, KITT, Leopold
  • Documentation: New "Running Unattended" section in README; QUICKSTART covers all three weather providers + scheduled events; ARCHITECTURE diagram now shows supervisor + heartbeat layers; CLAUDE.md catalogs the new modules

Configuration

New env vars (all optional, sensible defaults):

# Weather provider (auto-selects from configured vars if unset)
#WEATHER_PROVIDER=wttr             # or homeassistant / manual / none / auto
#HOME_ASSISTANT_URL=http://homeassistant.local:8123
#HOME_ASSISTANT_TOKEN=...
#HOME_ASSISTANT_WEATHER_ENTITY=weather.home
#MANUAL_WEATHER=Sunny and 72F

# Proactive scheduler (per-personality scheduled_events.yaml)
SCHEDULER_ENABLED=true
#QUIET_HOURS_START=22:00
#QUIET_HOURS_END=07:00

# Supervisor / watchdog (only relevant when running scripts/supervisor.py)
#HEARTBEAT_FILE=/tmp/jf_sebastian.heartbeat
#WATCHDOG_TIMEOUT=60.0
#RESTART_BACKOFF_INITIAL=1.0
#RESTART_BACKOFF_MAX=60.0
#CRASH_REPORT_DIR=./crash_reports/

See .env.example for the full annotated list.