diff --git a/home-manager/README.md b/home-manager/README.md index 2d8fbe4..c7a1348 100644 --- a/home-manager/README.md +++ b/home-manager/README.md @@ -40,7 +40,7 @@ Docs: https://hermes-agent.nousresearch.com/docs/user-guide/docker | `pvc.yaml` | 5 Gi PVC for `/opt/data` (HERMES_HOME) | | `deployment.yaml` | Deployment (1 replica, Recreate, pinned to amd64 NUC) + Service | | `ingress.yaml` | `home-manager.rogi.casa` → dashboard (TLS + basic auth) | -| `cron-seed.yaml` | one-shot Job that creates the Hermes cron schedule | +| `cron-seed.yaml` | Sync-hook Job that reconciles the Hermes cron schedule from Git (deletes + recreates each job on every run so prompt/schedule changes apply on sync) | | `README.md` | this file | --- @@ -105,8 +105,11 @@ Docs: https://hermes-agent.nousresearch.com/docs/user-guide/docker `argocd/apps/home-manager.yaml`). ArgoCD will create the namespace resources, deploy the pod, and bring up the ingress at `home-manager.rogi.casa`. 7. **Seed the cron jobs:** ArgoCD runs `cron-seed.yaml` as a Sync hook - automatically. To re-seed after a wipe: - `kubectl apply -f home-manager/cron-seed.yaml`. + automatically on every sync. The hook **reconciles** the cron schedule to + match Git: it deletes + recreates each named job, so prompt/schedule edits + in `cron-seed.yaml` take effect on the next sync (this overwrites any live + `hermes cron edit` changes — edit the prompts in Git instead). To re-run + it by hand: `kubectl apply -f home-manager/cron-seed.yaml`. 8. **Smoke test:** trigger a check manually — `kubectl exec -n home-manager deploy/hermes -- hermes cron run door-window-check` — and confirm the message lands in Discord (or `[SILENT]` if all clear). @@ -133,7 +136,11 @@ the agent is trusted. - **ArgoCD owns** (in git): namespace, RBAC, Secret, ConfigMap (seed), PVC, Deployment, Service, Ingress, cron-seed Job. - **Runtime state (on the PVC, NOT reconciled):** `config.yaml`, `SOUL.md`, - `.env`, `cron/jobs.json`, `sessions/`, `memories/`, `skills/`. The ConfigMap - only *seeds* these on first boot; after that, edits made via the dashboard or - `hermes cron edit` persist on the PVC and Argo will not revert them. For a - hard reset, delete the PVC and re-apply. + `.env`, `sessions/`, `memories/`, `skills/`. The ConfigMap only *seeds* these + on first boot; after that, edits made via the dashboard persist on the PVC + and Argo will not revert them. For a hard reset, delete the PVC and re-apply. + - **Exception — cron jobs (`cron/jobs.json`):** the `cron-seed` Sync hook + re-runs on every ArgoCD sync and **deletes + recreates** each named job from + `cron-seed.yaml`, so the cron schedule IS reconciled from Git. Live edits + via `hermes cron edit` will be overwritten on the next sync — edit the + prompts in `cron-seed.yaml` and commit instead. diff --git a/home-manager/configmap.yaml b/home-manager/configmap.yaml index 07e787b..c27bbb6 100644 --- a/home-manager/configmap.yaml +++ b/home-manager/configmap.yaml @@ -177,6 +177,18 @@ data: 8. **Respect time of day and presence.** Don't run noisy routines (loud media, vacuum robots) at night. Check presence (`zone.*` / `binary_sensor.*`) and the time before acting. + 9. **Absence of presence entities ≠ nobody home.** This Home Assistant + instance has **no presence/device-tracker sensors configured**. Do NOT + infer "nobody is home" from the absence of `zone.*`, `device_tracker.*`, + or presence `binary_sensor.*` entities, from `zone.home` being missing / + unavailable, or from presence sensors being absent. "No presence data" + means **unknown occupancy**, not "empty house". Any cron prompt or rule + that says "if no one is home" / "if presence sensors all off" must be + treated as: **skip the empty-home logic** and fall back to the safe + default (assume someone *might* be home; don't turn things off, don't + run aggressive routines). Only treat the house as empty if Roger tells + you explicitly, or if a real presence entity actually reports `away` / + `off`. ## How you reach Roger diff --git a/home-manager/cron-seed.yaml b/home-manager/cron-seed.yaml index 81f2422..5bbf55a 100644 --- a/home-manager/cron-seed.yaml +++ b/home-manager/cron-seed.yaml @@ -1,5 +1,9 @@ -# One-shot Job that seeds Hermes' built-in cron schedule on first install. -# Idempotent: skips job names that already exist. +# One-shot Job that seeds Hermes' built-in cron schedule. +# Reconciles: on every run it deletes any existing job with the same name and +# recreates it with the prompt/schedule below, so changes to this file (e.g. +# prompt wording) are applied to the live schedule on the next ArgoCD sync. +# Note: this means live `hermes cron edit` changes will be overwritten — edit +# the prompts here in Git instead and let ArgoCD reconcile. # # Cron prompts are deliberately written as plain-English instructions (no inline # curl commands) to avoid tripping Hermes' threat-pattern scanner, which blocks @@ -54,40 +58,44 @@ spec: POD=$(kubectl -n home-manager get pod -l app=hermes -o jsonpath='{.items[0].metadata.name}') echo "Using pod: $POD" + # Returns 0 if a cron job with this name already exists. exists() { kubectl -n home-manager exec "$POD" -- hermes cron list 2>/dev/null | grep -qi " $1 "; } + # Reconcile a cron job to the desired state defined in Git: if a job + # with this name already exists, delete it first so the (re)create + # below picks up prompt/schedule changes instead of being skipped. create() { name="$1"; schedule="$2"; deliver="$3"; prompt="$4" if exists "$name"; then - echo "cron job '$name' already exists — skipping" - else - echo "creating cron job '$name' ..." - kubectl -n home-manager exec "$POD" -- hermes cron create "$schedule" "$prompt" --name "$name" --deliver "$deliver" + echo "deleting existing cron job '$name' to apply updates ..." + kubectl -n home-manager exec "$POD" -- hermes cron delete "$name" || true fi + echo "creating cron job '$name' ..." + kubectl -n home-manager exec "$POD" -- hermes cron create "$schedule" "$prompt" --name "$name" --deliver "$deliver" } # ---- Watchdog checks (silent unless something is wrong) ---- create "door-window-check" "every 15m" "discord" \ - "Check the Home Assistant REST API as documented in your SOUL.md. List all door and window sensor entities (binary_sensor.* for doors, windows, contact sensors). If any are open and either no one is home (check zone.home or presence sensors) or it is between 23:00 and 06:00, notify Roger with which sensor is open. If all closed, reply with exactly [SILENT]." + "Check the Home Assistant REST API as documented in your SOUL.md. List all door and window sensor entities (binary_sensor.* for doors, windows, contact sensors). If any are open and it is between 23:00 and 06:00, notify Roger with which sensor is open. Note: this HA instance has NO presence sensors configured, so do NOT infer 'nobody is home' from missing/absent presence entities — only treat the house as empty if a real presence entity (zone.home / device_tracker / presence binary_sensor) exists AND reports away/off; otherwise occupancy is unknown and you must assume someone might be home. If all closed, reply with exactly [SILENT]." create "leak-moisture-check" "every 10m" "discord" \ "Check the Home Assistant REST API as documented in your SOUL.md. Look for moisture, water leak, or flood sensor entities (binary_sensor.* moisture/water/leak, sensor.* moisture). If any report wet/active, alert Roger immediately with the sensor name and location. If all dry, reply with exactly [SILENT]." create "energy-anomaly" "every 30m" "discord" \ - "Check the Home Assistant REST API as documented in your SOUL.md. Read the current power draw sensor (sensor.* power, sensor.* current_power). If the total power draw is unusually high for the time of day (over 4000 W during the day, or over 800 W when no one is home at night), alert Roger with the reading. Otherwise reply with exactly [SILENT]." + "Check the Home Assistant REST API as documented in your SOUL.md. Read the current power draw sensor (sensor.* power, sensor.* current_power). If the total power draw is unusually high for the time of day (over 4000 W during the day, or over 800 W at night), alert Roger with the reading. Otherwise reply with exactly [SILENT]." create "comfort-check" "every 1h" "discord" \ "Check the Home Assistant REST API as documented in your SOUL.md. Read indoor temperature and humidity sensors. If a room is outside a comfortable range (below 18C or above 26C, or humidity above 65 percent), you may toggle a fan entity in that room (safe). Do NOT change climate/HVAC settings yourself — if it is uncomfortable, propose a climate change to Roger on Discord and wait. If everything is comfortable, reply with exactly [SILENT]." create "left-on-check" "every 1h" "discord" \ - "Check the Home Assistant REST API as documented in your SOUL.md. If no one is home (zone.home is away or presence sensors all off), find any lights, media players, fans, or non-critical switches that are on. Turn off the safe non-essential ones (lights, media, fans) and report what you turned off. Do NOT touch anything security-critical. If someone is home, or nothing is left on, reply with exactly [SILENT]." + "Check the Home Assistant REST API as documented in your SOUL.md. IMPORTANT: this HA instance has NO presence sensors configured. Do NOT infer 'nobody is home' from missing/absent zone.home or presence entities. Only treat the house as empty if a real presence entity (zone.home / device_tracker / presence binary_sensor) exists AND reports away/off. If no such entity exists, occupancy is UNKNOWN — assume someone might be home and do NOT turn things off. Only when presence genuinely reports away/off: find any lights, media players, fans, or non-critical switches that are on, turn off the safe non-essential ones (lights, media, fans), and report what you turned off. Do NOT touch anything security-critical. If someone is home (or unknown), or nothing is left on, reply with exactly [SILENT]." # ---- Routines (always delivered) ---- create "goodnight-routine" "0 23 * * *" "discord" \ "Run the goodnight routine via the Home Assistant REST API as documented in your SOUL.md. Turn off all non-essential lights and pause media players (safe actions). Then propose — but do NOT execute — locking the doors (lock.*), closing the garage (cover.* garage), and arming the alarm (alarm_control_panel.*). Ask Roger on Discord for confirmation before calling any of those security-critical services. Deliver a short summary of what you turned off and what you are waiting for confirmation on." create "morning-briefing" "0 7 * * *" "discord" \ - "Produce a morning home briefing for Roger using the Home Assistant REST API as documented in your SOUL.md. Include: (1) who is home (zone.home / presence), (2) any lights or media still on, (3) any doors or windows open, (4) indoor temperatures, (5) today's calendar events if a calendar entity exists, (6) overnight energy use if an energy sensor exists, (7) any sensors in an alert/unavailable state. Keep it under 1800 chars. Always deliver (no [SILENT])." + "Produce a morning home briefing for Roger using the Home Assistant REST API as documented in your SOUL.md. Include: (1) occupancy status from zone.home / presence sensors IF such entities exist — if they do not exist, state 'presence: no presence sensors configured (occupancy unknown)' rather than claiming no one is home; (2) any lights or media still on, (3) any doors or windows open, (4) indoor temperatures, (5) today's calendar events if a calendar entity exists, (6) overnight energy use if an energy sensor exists, (7) any sensors in an alert/unavailable state. Keep it under 1800 chars. Always deliver (no [SILENT])." echo "Done. Listing all cron jobs:" kubectl -n home-manager exec "$POD" -- hermes cron list diff --git a/home-manager/rbac.yaml b/home-manager/rbac.yaml index 79a7fa4..6bec0bf 100644 --- a/home-manager/rbac.yaml +++ b/home-manager/rbac.yaml @@ -4,7 +4,8 @@ # via the Home Assistant REST API (http://home-assistant.home-assistant:80). # # The cron-seed Job needs to `kubectl exec` into the hermes pod to run -# `hermes cron create ...` (the only way to seed Hermes' internal cron). +# `hermes cron delete` + `hermes cron create ...` (the only way to seed and +# reconcile Hermes' internal cron schedule from Git). # Scoped to this namespace, pods/exec on the hermes pod only. --- apiVersion: v1