feat: no-progress detection watchdog — opencode stall detection, admin controls, desktop alerts #102
Reference in New Issue
Block a user
Delete Branch "feat/no-progress-lift-a"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
no-progress-lift-a: No-progress detection watchdog
What this adds
A local watchdog that catches opencode agent sessions spinning without progress, alerts the operator via desktop notify-send, surfaces stall data in the admin portal, and lets you block a stalled model with one click.
Components
src/progress_detect.py— Pure-stdlib detector: dup/top/slow/coverage signalssrc/notifier.py— Desktop notification channel (notify-send)src/watchdog.py— Main orchestrator with--oncemode, lock file, DB writessrc/watchdog_store.py— DB table management (ticks, verdicts, alerts, channels)admin/frontend/controls.html— Watchdog card, channels, test alert buttonadmin/frontend/index.html— Loops panel with open alertsadmin/frontend/models.html— Per-model stall rollup, Block/Unblockconfig/schema.sql— 4 new tables (watchdog_ticks, watchdog_verdicts, watchdog_alerts, watchdog_channel_settings)deploy/llm-router-watchdog.{service,timer}— systemd units (NOT installed)Tests (24 commits, 2370 passing)
test_progress_detect.py— detector signals + fixture labels (15 sessions)test_progress_backtest.py— backtest script + subprocess executiontest_config_watchdog.py— config validationtest_notifier.py— notify-send stubs, rate limitingtest_watchdog.py— orchestrator with stubbed opencode HTTPtest_admin_knob_coverage.py— knob coverage updatedManual QA verified on 8081
{"sent":1}— real notify-send firesPOSTblocks,DELETEunblocksOperator steps (post-merge)
systemctl --user daemon-reloadsystemctl --user start llm-router-watchdog.timersystemctl --user enable llm-router-watchdog.timersystemctl --user status llm-router-watchdog.timerExport 15 labelled sessions (8 must-flag, 7 must-not-flag) from opencode SQLite DB through scripts/export_progress_fixture.py into tests/fixtures/progress/fixture.json. - Reads opencode ~/.local/share/opencode/opencode.db - Extracts tool calls per prototype's calls_of logic - Applies must_flag/must_not_flag labels per the plan brief - Content scrubbing: sha1[:8] for all args except detector targets (filePath, path, offset, limit, command, pattern, output_mode, include, tmux_command) - file_lines: {path: line_count|null} map for coverage detection - landed: boolean per prototype (edit/write with non-empty diff, or git commit with exit 0) - Atlas window rule tracked per session label - Pre-commit sensitive data scan - Compact JSON format for size