feat(hands,jobs): job-run watchdog reads job_runs and alarms on three failures in a row

This commit is contained in:
hh committed 2026-09-22 17:16:17 +00:00
1 parent 6e28ff1e5b
commit 415f49ccd7
5 files changed
+967

No files matched your search

+52
View File
@@ -268,6 +268,58 @@ no `VIBEGRAM_HB_TOKEN` both jobs do nothing and the log says once at startup
that the watchdog is not configured. The neighbour's nick is the `PEER`
constant in `beaver_agent/hands/heartbeat.py`.
## The job-run watchdog (`beaver_agent/jobs/runs.py`)
The scheduler has always written one row per run into `job_runs` - name,
status, the head of the traceback in `error`. Nobody ever read it. The channel
to the neighbour agent was dead for 38 hours straight, the scheduler recorded
that 231 times, and nothing reached a human: the outage surfaced ten days later,
by hand, out of curiosity. The record existed, the reader did not. This job is
the reader.
- `прогоны` (`:27` every hour) reads `job_runs` for the last 24 hours through
the same connection the scheduler itself writes with
(`run.conversations.db.session()` and the `JobRunRecord` model) - no second
truth about where the journal lives.
- An alarm is taken only off the **tail**: three or more `failed` in a row
counting back from a job's latest run, i.e. the job is *lying down* rather
than having blinked once. A single failure is a network hiccup, and waking
a human for it teaches them not to look.
- The alarm says the job's name, how many runs in a row died, when the series
started, and the **first line** of the error - never the traceback. Replayed
over the real outage it reads: «джоб `вайбграм` лежит 23 часа и дольше,
144 прогона подряд с 12.09 22:00 UTC: HubError: vibegram 530 bad_json». It repeats at most once a day per job, and
when the job recovers one short `отбой` line goes out and the counter resets.
- **An empty answer is a statement about the query, not about health.** Zero
rows in 24 hours (other than this job's own) means no job ran at all, and
that is its own alarm with its own text - the scheduler is what to look at,
not any one job. A failed query is no reading whatsoever: the run logs it and
stays silent, because a database that did not answer is not evidence of
health or of illness.
- Everything that goes out is masked (`mask`): the gateway's shared `redact`
plus two local rules for what it cannot catch by construction - a
`NAME=value` pair in the middle of a line (a traceback puts them there) and
the query string of any URL, which is dropped whole because the parameter
carrying the key can be named anything.
State between runs is `мета/бобер/.состояние/прогоны.json` (who was told and
when). A lost file means a lying job is reported again and an `отбой` for
something reported before the loss never comes - the evidence for it is gone.
Two blind spots, both deliberate:
- **The watchdog cannot report its own death.** Its own runs are excluded from
the count (`run.job.name`), because a job that is down will not file a report
about itself - an observer inside the object does not report the object's
death. If `прогоны` itself starts failing, nothing here will say so; that
needs an outside eye, like the watchdog ring above.
- **The window is 24 hours, so a job that runs less than three times a day can
never accumulate three consecutive failures inside it.** `отметка`,
`закрытие`, `голосовой` (daily) and `память` (weekly) are therefore
unwatched by this job in practice; `вайбграм`, `логи`, `куратор`, `досуг`,
`ротация` and `сторож` are inside it. Widening the counting window past the
query window is the fix, when it is worth it.
## Policy (`beaver_agent/policy.py`)
`bypassPermissions` everywhere; the boundary is `PreToolUse` rules declared