home manager crons fix

This commit is contained in:
Roger Oriol
2026-07-25 00:54:46 +02:00
parent 4694034a04
commit 842815d370
4 changed files with 46 additions and 18 deletions

View File

@@ -40,7 +40,7 @@ Docs: https://hermes-agent.nousresearch.com/docs/user-guide/docker
| `pvc.yaml` | 5 Gi PVC for `/opt/data` (HERMES_HOME) | | `pvc.yaml` | 5 Gi PVC for `/opt/data` (HERMES_HOME) |
| `deployment.yaml` | Deployment (1 replica, Recreate, pinned to amd64 NUC) + Service | | `deployment.yaml` | Deployment (1 replica, Recreate, pinned to amd64 NUC) + Service |
| `ingress.yaml` | `home-manager.rogi.casa` → dashboard (TLS + basic auth) | | `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 | | `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, `argocd/apps/home-manager.yaml`). ArgoCD will create the namespace resources,
deploy the pod, and bring up the ingress at `home-manager.rogi.casa`. 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 7. **Seed the cron jobs:** ArgoCD runs `cron-seed.yaml` as a Sync hook
automatically. To re-seed after a wipe: automatically on every sync. The hook **reconciles** the cron schedule to
`kubectl apply -f home-manager/cron-seed.yaml`. 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 — 8. **Smoke test:** trigger a check manually —
`kubectl exec -n home-manager deploy/hermes -- hermes cron run door-window-check` `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). 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, - **ArgoCD owns** (in git): namespace, RBAC, Secret, ConfigMap (seed), PVC,
Deployment, Service, Ingress, cron-seed Job. Deployment, Service, Ingress, cron-seed Job.
- **Runtime state (on the PVC, NOT reconciled):** `config.yaml`, `SOUL.md`, - **Runtime state (on the PVC, NOT reconciled):** `config.yaml`, `SOUL.md`,
`.env`, `cron/jobs.json`, `sessions/`, `memories/`, `skills/`. The ConfigMap `.env`, `sessions/`, `memories/`, `skills/`. The ConfigMap only *seeds* these
only *seeds* these on first boot; after that, edits made via the dashboard or on first boot; after that, edits made via the dashboard persist on the PVC
`hermes cron edit` persist on the PVC and Argo will not revert them. For a and Argo will not revert them. For a hard reset, delete the PVC and re-apply.
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.

View File

@@ -177,6 +177,18 @@ data:
8. **Respect time of day and presence.** Don't run noisy routines (loud 8. **Respect time of day and presence.** Don't run noisy routines (loud
media, vacuum robots) at night. Check presence (`zone.*` / media, vacuum robots) at night. Check presence (`zone.*` /
`binary_sensor.*`) and the time before acting. `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 ## How you reach Roger

View File

@@ -1,5 +1,9 @@
# One-shot Job that seeds Hermes' built-in cron schedule on first install. # One-shot Job that seeds Hermes' built-in cron schedule.
# Idempotent: skips job names that already exist. # 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 # Cron prompts are deliberately written as plain-English instructions (no inline
# curl commands) to avoid tripping Hermes' threat-pattern scanner, which blocks # 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}') POD=$(kubectl -n home-manager get pod -l app=hermes -o jsonpath='{.items[0].metadata.name}')
echo "Using pod: $POD" 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 "; } 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() { create() {
name="$1"; schedule="$2"; deliver="$3"; prompt="$4" name="$1"; schedule="$2"; deliver="$3"; prompt="$4"
if exists "$name"; then if exists "$name"; then
echo "cron job '$name' already exists — skipping" echo "deleting existing cron job '$name' to apply updates ..."
else kubectl -n home-manager exec "$POD" -- hermes cron delete "$name" || true
echo "creating cron job '$name' ..."
kubectl -n home-manager exec "$POD" -- hermes cron create "$schedule" "$prompt" --name "$name" --deliver "$deliver"
fi 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) ---- # ---- Watchdog checks (silent unless something is wrong) ----
create "door-window-check" "every 15m" "discord" \ 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" \ 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]." "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" \ 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" \ 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]." "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" \ 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) ---- # ---- Routines (always delivered) ----
create "goodnight-routine" "0 23 * * *" "discord" \ 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." "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" \ 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:" echo "Done. Listing all cron jobs:"
kubectl -n home-manager exec "$POD" -- hermes cron list kubectl -n home-manager exec "$POD" -- hermes cron list

View File

@@ -4,7 +4,8 @@
# via the Home Assistant REST API (http://home-assistant.home-assistant:80). # 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 # 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. # Scoped to this namespace, pods/exec on the hermes pod only.
--- ---
apiVersion: v1 apiVersion: v1