Three agent-worker fixes stacked on dev, all CI-green (run 1840). Ships to :latest for deploy.
Commits
98b2ac9 — DRY pass on agent/fc_agent (behavior-preserving). Consolidated genuine duplication into single-source helpers: worker._fail/_release/_stopped/_abort_if_stopped/_timed/_ewma, client._submit/_post_quiet, detectors.Proposers._top, config._bool_env.
91ea06b — Status shows smoothed jobs/min + downloads/min. The buffer/on GPU/downloaders tiles were instantaneous samples of state that flips many times a second, so a 3 s poll only caught noise (read as "frozen"/"jumpy"). Replaced with two EWMA-smoothed derived-rate tiles (GPU throughput + fetch throughput, which together show pipeline balance); new monotonic downloaded counter. Buffer stays as the occupancy bar; pool sizes demoted to the sub-line.
6282e75 — real start/stop state machine. The Status pill hung on "stopping" forever: the backend had no lifecycle state, so the UI faked "stopping" from !running && active>0, which wedged when a consumer hung mid-release + stop() blocked the handler on curator releases. Now the backend owns stopped→starting→running→stopping→stopped and drives every transition; stop() returns instantly with a background monitor that lands on stopped bounded by a 20 s timeout (can never hang); starting→running only once curator actually answers; buttons gated by state; Start/Stop presses + every transition are logged.
Deploy note
Agent runs :latest (main), so this needs main + a re-pull to take effect on the desktop agent. No migrations; web/backend unaffected.
Three agent-worker fixes stacked on `dev`, all CI-green (run 1840). Ships to `:latest` for deploy.
## Commits
- **`98b2ac9` — DRY pass on `agent/fc_agent`** (behavior-preserving). Consolidated genuine duplication into single-source helpers: `worker._fail/_release/_stopped/_abort_if_stopped/_timed/_ewma`, `client._submit/_post_quiet`, `detectors.Proposers._top`, `config._bool_env`.
- **`91ea06b` — Status shows smoothed jobs/min + downloads/min.** The `buffer`/`on GPU`/`downloaders` tiles were instantaneous samples of state that flips many times a second, so a 3 s poll only caught noise (read as "frozen"/"jumpy"). Replaced with two EWMA-smoothed derived-rate tiles (GPU throughput + fetch throughput, which together show pipeline balance); new monotonic `downloaded` counter. Buffer stays as the occupancy bar; pool sizes demoted to the sub-line.
- **`6282e75` — real start/stop state machine.** The Status pill hung on "stopping" forever: the backend had no lifecycle state, so the UI faked "stopping" from `!running && active>0`, which wedged when a consumer hung mid-release + `stop()` blocked the handler on curator releases. Now the backend owns `stopped→starting→running→stopping→stopped` and drives every transition; `stop()` returns instantly with a background monitor that lands on `stopped` bounded by a 20 s timeout (can never hang); `starting→running` only once curator actually answers; buttons gated by state; Start/Stop presses + every transition are logged.
## Deploy note
Agent runs `:latest` (main), so this needs `main` + a re-pull to take effect on the desktop agent. No migrations; web/backend unaffected.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The buffer / on-GPU / downloader counts flip many times a second, so a 3s
status poll only ever samples noise — the tiles looked frozen (same value
twice) or random (wildly different), reading as "the Status section doesn't
update" when the backend was in fact live (operator-flagged 2026-07-01).
Replace the three instantaneous gauge tiles with two derived RATE tiles:
- jobs / min — GPU throughput, from the monotonic `processed` counter
- downloads / min — fetch throughput, from a new monotonic `downloaded`
counter (bumped when a job is decoded into the buffer)
Together they also show pipeline balance (dl/min > j/min ⇒ GPU-bound; the
reverse ⇒ GPU starved). Both are EWMA-smoothed over the poll deltas, clamped
at 0 (agent restart resets the counters), and skip a backgrounded-tab gap.
The still-useful instantaneous state is demoted, not lost: buffer stays as
the occupancy bar; downloaders/consumers/on-GPU move to the sub-line. `waited
out` (transient) gets promoted to a tile.
backend: worker.status() gains `downloaded`; `_bump(downloaded=)`.
frontend: retiled Status + rate math in applyStatus; VERSION → .7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Status pill hung on "stopping" forever (operator-flagged 2026-07-01).
Root cause: the backend had no lifecycle state — status() only returned
running/stopped — so the UI FABRICATED "stopping" in JS as `!running &&
active>0`. That pill only cleared when the backend's `active` counter hit 0,
but stop() (a) blocked the HTTP handler on lease-release calls to curator and
(b) left `active>0` whenever a consumer wedged mid-submit/release to an
overloaded curator → "stopping" that never resolved.
Give the backend a real, truthful state it drives itself:
stopped → starting → running → stopping → stopped
- start(): → starting; a downloader flips it to running on its FIRST
successful lease (so "running" means curator is actually answering, not
just "Start was clicked"). If curator's down it honestly stays "starting".
- stop(): → stopping; returns immediately (no handler block). A background
monitor waits for the worker threads to actually exit, releases leases,
then → stopped — bounded by STOPPING_TIMEOUT (20s) so a wedged submit can
NEVER hold the UI in "stopping" again. In-flight work is handed back safely.
- Buttons follow the real state (Start only from stopped; both disabled
through the transition), so you can't fight a transition.
- Log every Start/Stop button press (routes) and every transition (worker),
so the Logs panel shows exactly what each button did.
Frontend now trusts s.state (drops the active>0 hack); VERSION → .8.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Three agent-worker fixes stacked on
dev, all CI-green (run 1840). Ships to:latestfor deploy.Commits
98b2ac9— DRY pass onagent/fc_agent(behavior-preserving). Consolidated genuine duplication into single-source helpers:worker._fail/_release/_stopped/_abort_if_stopped/_timed/_ewma,client._submit/_post_quiet,detectors.Proposers._top,config._bool_env.91ea06b— Status shows smoothed jobs/min + downloads/min. Thebuffer/on GPU/downloaderstiles were instantaneous samples of state that flips many times a second, so a 3 s poll only caught noise (read as "frozen"/"jumpy"). Replaced with two EWMA-smoothed derived-rate tiles (GPU throughput + fetch throughput, which together show pipeline balance); new monotonicdownloadedcounter. Buffer stays as the occupancy bar; pool sizes demoted to the sub-line.6282e75— real start/stop state machine. The Status pill hung on "stopping" forever: the backend had no lifecycle state, so the UI faked "stopping" from!running && active>0, which wedged when a consumer hung mid-release +stop()blocked the handler on curator releases. Now the backend ownsstopped→starting→running→stopping→stoppedand drives every transition;stop()returns instantly with a background monitor that lands onstoppedbounded by a 20 s timeout (can never hang);starting→runningonly once curator actually answers; buttons gated by state; Start/Stop presses + every transition are logged.Deploy note
Agent runs
:latest(main), so this needsmain+ a re-pull to take effect on the desktop agent. No migrations; web/backend unaffected.🤖 Generated with Claude Code
The buffer / on-GPU / downloader counts flip many times a second, so a 3s status poll only ever samples noise — the tiles looked frozen (same value twice) or random (wildly different), reading as "the Status section doesn't update" when the backend was in fact live (operator-flagged 2026-07-01). Replace the three instantaneous gauge tiles with two derived RATE tiles: - jobs / min — GPU throughput, from the monotonic `processed` counter - downloads / min — fetch throughput, from a new monotonic `downloaded` counter (bumped when a job is decoded into the buffer) Together they also show pipeline balance (dl/min > j/min ⇒ GPU-bound; the reverse ⇒ GPU starved). Both are EWMA-smoothed over the poll deltas, clamped at 0 (agent restart resets the counters), and skip a backgrounded-tab gap. The still-useful instantaneous state is demoted, not lost: buffer stays as the occupancy bar; downloaders/consumers/on-GPU move to the sub-line. `waited out` (transient) gets promoted to a tile. backend: worker.status() gains `downloaded`; `_bump(downloaded=)`. frontend: retiled Status + rate math in applyStatus; VERSION → .7. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>