# Cron Job: pm-heartbeat-weekday

**Job ID:** e356fa099ee4
**Run Time:** 2026-07-05 20:03:37
**Schedule:** 0 20 * * 0-4

## Prompt

[IMPORTANT: The user has invoked the "pm-heartbeat" skill, indicating they want you to follow its instructions. The full skill content is loaded below.]

---
name: pm-heartbeat
description: Morning chase digest - Hermes cron reads the Actions register + Daily Records and pushes a short Telegram digest to Rocky (overdue, due-soon, aged-waiting, live jobs gone quiet). READ-ONLY. The autonomous pulse of the /pm cockpit.
runtime: Hermes cron (06:00 Sydney weekdays, 08:00 Sydney weekends)
inputs: Airtable Actions register + Daily Records (base appE43UvTyARe5oJs)
outputs: short Telegram digest to channel 7377499346
failure-mode: "heartbeat up, register unreachable" — never silence
---

# pm-heartbeat — the morning pulse

The one thing that makes the system speak first. /pm keeps the Actions
register true; this skill reads it every morning and pushes what matters
to Rocky's Telegram before he opens anything. **READ-ONLY — this skill
never writes to Airtable. Ever.**

## Runtime

- **Schedule**: 06:00 AEST weekdays, 08:00 AEST weekends (cron, UTC).
- **Auth**: PAT loaded from `~/.hermes/config.yaml` →
  `mcp_servers.airtable.env.AIRTABLE_API_KEY` (NOT shell env). See
  `references/airtable-recipe.md`.
- **Deliver**: `telegram:7377499346` (Rocky's home channel — fixed binding
  for cron sessions that have no current-chat context).
- **Failure mode**: if Airtable raises an exception, send exactly
  `"heartbeat up, register unreachable"`. A silent morning means nothing
  to chase — NEVER that the cron died.

## The read

1. Actions register: rows with `Status = Open` or `Waiting`.
2. Jobs: Lifecycle Status (for the QUIET band) + Job Number lookup.
3. Daily Records: latest Diary Ref date per live job.

Field IDs, query recipes, and band logic: `references/airtable-recipe.md`.

## The digest (caveman-short, max 15 lines, worst first)

```
PM 06:00 - 3 chase, 2 due, 1 quiet
OVERDUE: 2649 Clontarf bid - due YESTERDAY
2626 Jalen start-date reply - waiting 9d (chase: Jalen, email)
DUE <=7d: 2654 takeoff back from Assad - Mon 7/7
2630 subcontract signing - Fri 11/7
QUIET: 2631 Hornsby on-site, no diary 4d
```

Band order (cap each at 5 lines): **OVERDUE** → **AGED WAITING** →
**DUE ≤7d** → **QUIET**. Empty morning = one line:
`PM 06:00 - nothing to chase. Register clean or register blind - /pm to verify.`

**No dollar figures in the digest** (phone-lock-screen surface).
Job number + action + age only.

## Deploy-time binding

- Base `appE43UvTyARe5oJs` (LFCS-Operations)
- Actions `tblwGQdNqZIPRiDSV`, Jobs `tblcL2EX2w34c1VPe`,
  Daily Records `tbli76T3GvmLSd3Kd`
- Telegram channel `7377499346`

## Smoke-test before arming cron

Before creating the cron job, live-test the read logic in a foreground
session with the user watching. See `lfcs/references/cron-smoke-test.md`
for the full protocol and reusable Python snippets. Five-minute check;
prevents silent misses.

## Deploy note (2026-07-03)

Cron IDs:
- weekday: `e356fa099ee4` (next run `0 20 * * 1-5` UTC = 06:00 AEST)
- weekend: `6864bbd32cdc` (next run `0 22 * * 6,0` UTC = 08:00 AEST)

Sydney is UTC+10 (no DST). Hermes cron whitelist does not include
`CRON_TZ` for `Australia/Sydney`, so cron is in UTC. If AEST/AEDT
shifts after a future DST rule change, recompute the UTC offsets.

Two data gaps surfaced during smoke test (both need OpsMan follow-up):
- 22 of 25 jobs have no Lifecycle Status set → QUIET band infers "live"
  from active Action presence as a workaround.
- Daily Records has only 2 rows in the table — either OpsMan DR sync
  is lagging, or DRs go elsewhere now. Either way, the digest falls
  back to "active Actions = live job".

The user has provided the following instruction alongside the skill invocation: [IMPORTANT: You are running as a scheduled cron job. DELIVERY: Your final response will be automatically delivered to the user — do NOT use send_message or try to deliver the output yourself. Just produce your report/output as your final response and the system handles the rest. SILENT: If there is genuinely nothing new to report, respond with exactly "[SILENT]" (nothing else) to suppress delivery. Never combine [SILENT] with content — either report your findings normally, or say [SILENT] and nothing more.]

You are running the pm-heartbeat skill. Load it with skill_view(name='pm-heartbeat') and execute its digest logic exactly as written. Read Airtable base appE43UvTyARe5oJs (PAT loads from ~/.hermes/config.yaml → mcp.servers[airtable-mcp-server].env.AIRTABLE_API_KEY, do NOT print it). Query the Actions register (tblwGQdNqZIPRiDSV) and Daily Records (tbli76T3GvmLSd3Kd), build the caveman-short digest per the bands in the skill, and deliver ONLY to telegram:7377499346. If Airtable is unreachable, send exactly: "heartbeat up, register unreachable". Read-only - never write Airtable. Reply with the digest body as your final message; the system will deliver it automatically.

## Response

15 lines exactly. Sits within budget. This is the digest body. The system will deliver it to telegram:7377499346 automatically.

PM 08:00 Sun 5/7 - 11 overdue, 2 aged-waiting, 26 due<=7d, 1 quiet
OVERDUE:
2648 Ward Park - send place-only (due Sat 4/7)
2631 Hornsby - buy Ken rev quote on Liam OK
2631 Hornsby - verify concrete Mon 6/7 blinding
2650 Macarthur - verify outcome, ask Garry Makkar
AGED WAITING:
2633 Tamplin - 6 wk silent, chase Christian
2651 Camellia - Ray dig SENT 29/6, no reply
DUE <=7d:
2649 Clontarf - seats IN, send 2 held (bid TOMORROW)
2649 Shaun (Delta) - K&G + Aug validity (today)
2649 Liam go/no-go -> final pack -> Rocky sends
QUIET:
2631 Hornsby on-site, no diary ~13 wd - DR sync gap
