# Docs pass after Lift A (PR #102) Status: done -- executed on branch docs/lift-a-docs-pass Date: 2026-09-26 This brief is the contract. Keep the plan small: one todo per doc, each with exact section anchors. Do NOT read `plans/no-progress-detection.md` (reference only). ## Goal Bring the docs in line with what PR #102 shipped and what is running now. The docs describe the code as it is on this branch; where a doc and the code disagree, the code wins, and the todo says which line it checked. ## Where the work happens - Worktree: `/home/alee/Sources/6krrt-worktrees/docs-lift-a-pass`, branch `docs/lift-a-docs-pass`, based on `origin/main` `a24de1b` plus three commits that already carry North Star #4 in `CLAUDE.md`, the incident #8 rewrite in `docs/incidents.md`, and the plans. Build on them; do not rewrite them. - **Docs only.** No code, config, test or unit-file changes. - **Do not touch** `README.md`, `AGENTS.md`, `deploy/README.md`: the owner has uncommitted edits to them in the main checkout. Anything that belongs in `deploy/README.md` goes in `docs/watchdog.md` instead, and the done report lists it for the owner to fold in. ## What shipped (verify each against the code before writing it) - `src/progress_detect.py`: pure detector. Signals dup, top, slow, coverage; landed gating; read-only agents; canonical (sorted-key) args; bash command normalization (comment lines, `cd dir &&`, `VAR=x` prefixes dropped). - `src/watchdog.py` + `deploy/llm-router-watchdog.{service,timer}`: 5-minute oneshot. Judges only sessions with a tool call since the last tick, on their full history, per session; landed counts the session's own descendants only. Alert lifecycle in `watchdog_alerts`: trigger once, escalate once (LLM yes or 3 ticks), resolve once. Quiet `no_opencode` tick when opencode is closed. Optional local-model second opinion (does not veto). - `src/notifier.py`: `desktop` channel (`notify-send`), PagerDuty-Events-v2 shaped `AlertEvent`, per-channel `min_severity`, rate limit backed by the DB. - `src/watchdog_store.py`: tables `watchdog_ticks`, `watchdog_verdicts`, `watchdog_alerts`, `watchdog_channel_settings`. - Admin: Loops panel (`index.html#loops`), Watchdog card on Controls (last tick, verdicts, channels, Send test alert), per-model stall rollup and Block/Unblock on Models. New availability value `blocked`, excluded from routing and from pinned requests via `_admin_excluded_models`. - Endpoints: `GET /admin/api/watchdog/status`, `POST /admin/api/watchdog/channels`, `POST /admin/api/watchdog/test-alert`; availability endpoint accepts `blocked`. - Config: `watchdog.*` (incl. `detector.*`, `dashboard_base_url`), `notifications.channels`. Watchdog knobs are persisted-only (separate process). - `deploy/opencode-plugin/router-link.js`: the `parentCache` export made opencode 1.18 reject the whole plugin ("Plugin export is not a function" in `~/.local/share/opencode/log/opencode.log`), so identity headers never reached the router from #99 until this fix. A loader-contract test now asserts every export is a function. - `scripts/progress_backtest.py` (`--fixture` replays the 15 labelled sessions; expect 8/15 flagged) and `scripts/export_progress_fixture.py`. - Operator state 2026-09-26: timer enabled ~15:18 EDT with unit paths repointed from `%h/llm-router` to `%h/Sources/6krrt` (production still runs from the dev checkout; see `plans/deploy-separation.md`); plugin fix installed and opencode restarted 15:19; router restarted 15:19:57. ## Todos (one per doc) 1. **New `docs/watchdog.md`**: what it catches and does not (steady spend is not waste), signals and thresholds, alert lifecycle, the admin surfaces, knobs, install (the `sed` repoint + `enable --now`), how to run `--once`, backtest, re-exporting the fixture, and known limits: sessions under 40 calls are invisible; the Atlas must-flag case sits exactly at `cover_min` 4.0; the stale `localhost:4096` entry in `rc-servers.json` logs a harmless probe warning. 2. **`CLAUDE.md`**: "What's built and working" (anchor `## What's built and working`): add the modules above in the existing one-bullet style, linking `docs/watchdog.md`; update the `tests/` bullet count from the real `pytest --collect-only -q` total. "Run as a service" (anchor `## Run as a service`): one short paragraph on the watchdog timer. Do not edit the North Star section. 3. **`docs/incidents.md` #8**: under Status, add that Lift A shipped (PR #102, `a24de1b`) and the timer is live; add the plugin-loader finding as Evidence (with the log line) and to the Human response timeline; add a symptom-table row "opencode plugin does nothing -> grep opencode.log for `failed to load plugin`". Keep the Evidence / labelled Theory convention in the file header. 4. **`docs/admin-portal.md`**: sections for the Loops panel, the Watchdog card, and the Models rollup with Block/Unblock (where `blocked` differs from `deprecated`: an operator's reasoned stop, one click to undo). 5. **`docs/api.md`**: the three watchdog endpoints and `blocked` on the availability endpoint (request/response shapes read from `src/admin.py`). 6. **`docs/data-model.md`**: the four watchdog tables (from `config/schema.sql`), and `blocked` in the `availability` row at the `models` section (line ~34) and wherever `admin_model_overrides` is described. 7. **`docs/clients.md`** (anchor `## Session-directory attribution (opencode plugin)`) and `docs/api.md` near the `X-Router-*` table (line ~151): the plugin loader contract (every export must be a function) and how to confirm the plugin loaded. ## Rules - ASCII only; no middle dots. Match each file's existing style and heading depth. Admin-portal docs describe the UI, they do not add prose to it. - Numbers and names come from the code on this branch, never from this brief. - Commit per todo, by explicit path. Never `git add -A`. - Do not push and do not open a PR. The done report lists commits and the items for `deploy/README.md`. - If a worker's context passes ~150k tokens, stop it and start a fresh one. Write `.omo/plans/docs-lift-a-pass.md`, then stop and report.