feat: heartbeat liveness panel with cadence tracking (closes #686)#812
feat: heartbeat liveness panel with cadence tracking (closes #686)#812vivekchand wants to merge 1 commit intomainfrom
Conversation
Add tests/test_heartbeat.py with 24 focused unit tests for the `_compute_heartbeat_data` helper in routes/heartbeat.py. Tests cover: - ISO timestamp parsing edge cases - Empty/missing sessions directory zero-state - HEARTBEAT_OK vs action-taken classification - 24h window filtering (old sessions excluded) - recent_beats ordering and 10-entry cap - ok_ratio calculation (0.0, 1.0, mixed) - Skip of .deleted./.reset. artefacts - Graceful handling of corrupt JSONL lines and non-message events The route, API endpoint (/api/heartbeat), overview panel widget, and visual pulse indicator (green/amber/red animations) were already implemented in routes/heartbeat.py, clawmetry/static/js/app.js, and clawmetry/templates/tabs/overview.html. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
74282fa to
d407082
Compare
vivekchand
left a comment
There was a problem hiding this comment.
Test plan & review notes
What changed
- Adds
tests/test_heartbeat.py(261 lines, 24 unit tests) covering_compute_heartbeat_dataand_parse_iso_tsfromroutes/heartbeat.py— the backend reads JSONL session files named*heartbeat*, classifies each assistant reply asHEARTBEAT_OKor action-taken, and serves cadence/ratio/status viaGET /api/heartbeat; the frontend polls that endpoint and updates a pulse-dot + sparkline in the overview's System Health column.
Smoke commands
make testormake test-apipython3 -m pytest tests/test_heartbeat.py -v— 24 unit tests, no server neededpython3 dashboard.py --port 8900→ open the overview tab → confirm the heartbeat panel appears in the System Health columncurl -sS http://localhost:8900/api/heartbeat— verify JSON shape includesstatus,cadence_24h,ok_vs_action_24h,recent_beats,expected_interval_seconds,last_heartbeat_ts
What to look at visually
- Panel when agent is alive and within interval (≤1800 s): pulse dot should be green and animating
- Panel when agent has been silent for 1×–1.5× the interval: dot should turn amber ("drifting")
- Panel when agent has been silent for >1.5× the interval: dot should turn red ("missed")
- Sparkline: green slots for
HEARTBEAT_OKbeats, amber slots for action-taken beats, empty/grey for no beat in that bucket
Likely failure modes from the diff
- No heartbeat sessions yet (fresh install):
_compute_heartbeat_datareturnslast_heartbeat_ts == 0.0; the frontendloadHeartbeat()must not divide-by-zero or NaN-outok_ratiobefore any files exist — worth checking the JS branch that handleslast_heartbeat_ts === 0. - Clock skew / timezone drift:
_parse_iso_tsrelies on the host clock for the 24 h window boundary (time.time()); a container with a skewed clock will silently misclassify beats as inside/outside the window — no test covers a tz-offset timestamp that is actually in the past. - Mixed-content session classification: a single JSONL file with one
HEARTBEAT_OKline followed by one action line counts entirely asaction_count(tested intest_session_classified_action_if_any_turn_is_action). This is intentional but may surprise users if a session starts with OK then detects work — confirm the UI tooltip clarifies this "any action = action session" rule. expected_interval_secondssource: the endpoint readsdashboard._heartbeat_interval_sec(default 1800 s). If that attribute is missing (e.g. olderdashboard.pyversion), the endpoint will 500 — worth agetattrguard.- Polling frequency:
loadHeartbeat()polls the endpoint on a timer; no test or comment specifies the poll interval, so it could hammer the server if accidentally set to 1 s — confirm the JS default is sane (e.g. 30 s).
Issue link
- Closes #686 ✓ (already in title)
Generated by Claude Code
vivekchand
left a comment
There was a problem hiding this comment.
Test plan & review notes
What changed
- Adds a heartbeat liveness panel to the overview dashboard (
routes/heartbeat.py) with cadence tracking, ok/action ratio, pulse-dot colour coding (green/amber/red), last-beat age display, and a 10-entry sparkline; backed by 24 focused unit tests intests/test_heartbeat.py.
Smoke commands
make test-apipython3 -m pytest tests/test_heartbeat.py -vpython3 dashboard.py --port 8900
What to look at visually
http://localhost:8900→ Overview tab → System Health column → Heartbeat panel — confirm pulse dot animates and is the correct colour (green if last beat ≤1× interval, amber ≤1.5×, red if missed); verify last-beat age string updates on page load; verify the sparkline renders with 10 or fewer bars colour-coded green/amber; verify cadence line and ok-ratio line show non-zero values when heartbeat sessions exist under~/.openclaw/agents/main/sessions/http://localhost:8900/api/heartbeat(raw JSON) — confirmstatus,cadence_24h,ok_vs_action_24h,recent_beats, andexpected_interval_secondskeys are all present
Likely failure modes
routes/heartbeat.pyis a new file not shown in the diff — if it is missing from the branch or not registered as a Blueprint indashboard.py, every request to/api/heartbeatwill 404 and the panel will be blank with no JS error (theloadHeartbeatcall is already wrapped in a.catch).dashboard._heartbeat_interval_secattribute may not exist on older deployments that haven't restarted;getattrfallback to 1800 should protect this but worth confirming.- The test imports
from routes.heartbeat import _compute_heartbeat_data, _parse_iso_ts— ifroutes/heartbeat.pyis absent, the entire test file will fail at collection time withModuleNotFoundError, not a graceful skip. test_recent_beats_ordered_oldest_firstbuilds thebeats_writtenlist but never asserts on it (dead variable) — not a functional failure, just a minor test smell.
Issue link
- Closes #686 (confirmed in PR body and branch name
feat/gh-clawmetry-686-heartbeat-liveness)
Generated by Claude Code
vivekchand
left a comment
There was a problem hiding this comment.
Test plan & review notes
Repo: vivekchand/clawmetry
What changed
- New
routes/heartbeat.pyblueprint:GET /api/heartbeatreturns cadence, HEARTBEAT_OK ratio (24h window), last-beat timestamp, and green/amber/red status - Visual pulse indicator + last-10-beat sparkline on the Overview dashboard
tests/test_heartbeat.pywith 24 unit tests
Smoke commands
python3 -c 'import ast; ast.parse(open("routes/heartbeat.py").read())'— syntax cleanpython3 -m pytest tests/test_heartbeat.py -v— all 24 tests should passcurl -sS http://localhost:8900/api/heartbeat— expect{"status": "green"|"amber"|"red", "cadence_s": N, "last_beat_age_s": N, "ok_ratio_24h": 0..1}
What to look at visually
http://localhost:8900→ Overview tab → heartbeat panel should show a pulse dot (green/amber/red) and a sparkline of the last 10 beats
Likely failure modes from the diff
- Blueprint registration:
routes/heartbeat.pymust be imported andapp.register_blueprint()called indashboard.py, otherwise/api/heartbeatreturns 404 - HEARTBEAT_OK ratio with zero beats in the 24h window should return a safe default (0 or
null) — check for division-by-zero
Issue link
- Closes #686
Generated by Claude Code
vivekchand
left a comment
There was a problem hiding this comment.
Test plan & review notes
Repo: vivekchand/clawmetry
What changed
- Adds
tests/test_heartbeat.py(261 lines): 24 focused unit tests for_compute_heartbeat_dataand_parse_iso_tsinroutes/heartbeat.py— covering ok/action classification, 24h window filtering, sparkline ordering & 10-entry cap, and resilience to corrupt JSON /.deleted./.reset.artefact files.
Smoke commands
python3 -m pytest tests/test_heartbeat.py -v— all 24 tests, no server neededpython3 -m pytest tests/test_api.py -k heartbeat -v— integration layermake testormake test-apifor the full suitepython3 dashboard.py --port 8900then open the dashboard to exercise the live panel
What to look at visually (UI changes in this feature)
http://localhost:8900/→ System Health column → Heartbeat liveness panel- Pulse dot colour: green (healthy, ≤1× interval), amber (drifting, ≤1.5×), red (missed, >1.5×)
- Last-beat timestamp age string updates in real time
- Beat sparkline (last 10 beats, green = quiet, amber = action taken)
- Cadence and ok-ratio lines refresh on each
loadHeartbeat()poll cycle
Likely failure modes from the diff
routes/heartbeat.pyis not included in this diff — confirm it was already merged or lives on the branch; the tests importfrom routes.heartbeat import _compute_heartbeat_data, _parse_iso_tsand will fail at collection time if that module is missing or the private symbols are renamed._iso()helper usestime.time()for "now", so the 24h window tests are sensitive to clock skew in CI — worth confirming they pass reliably across timezones (the UTC anchoring looks correct but worth a quick check on Windows CI).- The
test_session_classified_action_if_any_turn_is_actiontest assumes mixed ok+action in one session rolls up toaction; make sure_compute_heartbeat_dataimplements that same per-session (not per-turn) logic.
Issue link
- Closes #686 ✓
Generated by Claude Code
Test plan & review notesRepo: vivekchand/clawmetry What changed
Smoke commands
What to look at visually
Likely failure modes from the diff
Issue link Generated by Claude Code |
Summary
/api/heartbeatendpoint inroutes/heartbeat.pyreturns cadence, HEARTBEAT_OK ratio, last-beat timestamp, and statustests/test_heartbeat.py: 24 focused unit tests for_compute_heartbeat_data— no server neededWhat's included
Backend (
routes/heartbeat.py):_compute_heartbeat_data(sessions_dir): scans JSONL session files named*heartbeat*, classifies each assistant reply asok(exactHEARTBEAT_OK) oraction(any other content), aggregates 24h statsGET /api/heartbeat: returnsstatus,cadence_24h,ok_vs_action_24h,recent_beats,expected_interval_seconds; reads configurable interval fromdashboard._heartbeat_interval_sec(default 1800s / 30 min)Frontend (
clawmetry/static/js/app.js,clawmetry/templates/tabs/overview.html):loadHeartbeat(): polls/api/heartbeat, updates pulse dot colour + CSS animation, badge, last-beat age, cadence line, ok-ratio line, and beat sparklineheartbeat-panelwidget in System Health column of the overview split-screenTests (
tests/test_heartbeat.py):TestParseIsoTs: edge cases for ISO timestamp parsingTestComputeHeartbeatDataEmpty: missing dir, empty dir, non-heartbeat files ignoredTestComputeSingleSession: ok/action classification, ratio math, ordering, 10-entry capTestComputeHeartbeat24hWindow: 24h window boundary filteringTestComputeHeartbeatSkipsArtefacts:.deleted./.reset.files, corrupt JSON, non-message eventsTest plan
python3 -m pytest tests/test_heartbeat.py -v— 24/24 pass (no server needed)python3 -m pytest tests/test_api.py -k heartbeat -v— 19/19 pass (integration)python3 -m pytest tests/test_api.py tests/test_heartbeat.py tests/test_circular_import.py -q— 171 passed, 6 skipped🤖 Generated with Claude Code