You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A cog whose work loop wedges — but whose process stays alive and keeps answering HTTP — is never detected or restarted. The agent treats "process is up / HTTP responds" as healthy, so a wedged cog stays "green" indefinitely.
This is the sibling failure mode to #52. That issue covers a cog that crashes/exits (segfault → zombie → not restarted). This one is the opposite: the cog never exits — it keeps returning HTTP 200 on / and /health — but its internal work loop has stalled and stopped making progress. Any supervision keyed on process-exit (the reap + auto-restart-on-crash that #52 asks for) cannot see this case, because there is nothing to reap.
Concrete instance (the doom cog, but the failure mode is cog-agnostic):
The cog renders frames on a dedicated worker thread and publishes them behind a counter. After ~38h uptime (~6.4M frames) the worker thread wedged in its paced-sleep cycle and stopped producing frames.
The cog's HTTP server thread stayed alive the whole time: GET / served the full page, and GET /health kept returning {"status":"ok"}.
From a client this looks "stuck" — the page loads but never updates — rather than down, so nothing (and no one) flags it.
It only recovered after a manualsystemctl restart of the cog. CPU was ~2% the whole time (a wedge, not a runaway loop), so even a CPU-based heuristic wouldn't catch it.
Why the current health surface doesn't help:
/health is app-controlled and trivial. A cog returning a static {"status":"ok"} proves only that its HTTP thread is scheduled — not that its actual work is progressing. process-alive ≠ work-progressing, and the agent currently can't tell the difference.
Give the agent a liveness signal independent of "process up / HTTP 200", and recover on staleness. Options, roughly in order of effort:
Liveness convention for cogs: define an optional cog-reported heartbeat / monotonic work counter (e.g. a required field in /health like {"status":"ok","ticks":<n>,"last_progress_ms":<n>}). The agent polls it and restarts the cog if the counter hasn't advanced within a cog-declared interval.
Agent-side staleness watchdog: for cogs that expose a frame/progress endpoint, the agent samples it and restarts on no-progress, with backoff and a clear "stalled/restarting" status (mirroring the status fix requested in Cog supervision: crashed cog not auto-restarted, left as a zombie #52).
Document the expectation for cog authors: a meaningful /health must reflect work progress, not just thread liveness — so this failure mode is designed out rather than discovered in the field.
(The cog-side half of this — making the doom cog's own /health report frame liveness and self-exit on a stalled render loop so the supervisor can restart it cleanly — is being handled in cognitum-one/cogs#28. This issue tracks the platform-level gap that applies to every cog.)
Steps to reproduce
Run any cog with a long-lived worker loop that can stall without the process exiting (the doom cog reproduces it after long uptime; a worker thread blocking or wedging while the HTTP thread keeps serving is enough).
Let the worker loop wedge (or simulate it: have the worker stop advancing its progress counter while the HTTP server keeps answering).
Observe: GET /health still returns {"status":"ok"}, the process stays alive, GET /api/v1/apps shows the cog healthy — but the cog produces no new work.
Nothing auto-detects or restarts it; recovery requires a manual stop/start (or systemctl restart).
Logs / console output
# client view — frame counter frozen, server still serving the last frame
$ curl -s -D - -o /dev/null 'http://<seed>:1993/frame?since=0'
HTTP/1.1 200 OK
X-Frame-Id: 6442296 # identical across repeated requests, minutes apart
$ curl -s -D - -o /dev/null 'http://<seed>:1993/frame?since=6442296'
HTTP/1.1 204 No Content # no frame newer than the frozen one, ever
$ curl -s 'http://<seed>:1993/health'
{"status":"ok"} # still "healthy"
# on-device — worker thread alive but parked, process near-idle (wedge, not spin)
$ for t in /proc/<pid>/task/*; do printf '%s %s %s\n' \
"$(cat $t/comm)" "$(awk '{print $3}' $t/stat)" "$(cat $t/wchan)"; done
doom-engine S hrtimer_nanosleep # worker parked in its paced sleep, not drawing
cog-doom-arm S inet_csk_accept # HTTP accept thread — still up
cog-doom-arm S futex_wait_queue # HTTP workers — parked
...
# CPU delta over 2s: ~4 ticks (~2%) => wedged, not a runaway loop
(Cog-side stdout was unavailable — journald had rotated by the time it was noticed, which is itself part of the problem: there's no retained signal that the loop stalled.)
Firmware version
cognitum-agent v0.22.7 (mode: seed)
Hardware
Raspberry Pi Zero 2 W (Rev 1.0)
Host OS
Raspbian GNU/Linux 13 (trixie) — kernel
6.12.47+rpt-rpi-v7(armv7l)Description
A cog whose work loop wedges — but whose process stays alive and keeps answering HTTP — is never detected or restarted. The agent treats "process is up / HTTP responds" as healthy, so a wedged cog stays "green" indefinitely.
This is the sibling failure mode to #52. That issue covers a cog that crashes/exits (segfault → zombie → not restarted). This one is the opposite: the cog never exits — it keeps returning
HTTP 200on/and/health— but its internal work loop has stalled and stopped making progress. Any supervision keyed on process-exit (the reap + auto-restart-on-crash that #52 asks for) cannot see this case, because there is nothing to reap.Concrete instance (the
doomcog, but the failure mode is cog-agnostic):GET /served the full page, andGET /healthkept returning{"status":"ok"}.systemctl restartof the cog. CPU was ~2% the whole time (a wedge, not a runaway loop), so even a CPU-based heuristic wouldn't catch it.Why the current health surface doesn't help:
/healthis app-controlled and trivial. A cog returning a static{"status":"ok"}proves only that its HTTP thread is scheduled — not that its actual work is progressing.process-alive≠work-progressing, and the agent currently can't tell the difference.<defunct>cog child of the agent that was never reaped), so Cog supervision: crashed cog not auto-restarted, left as a zombie #52 is live too — but it's a separate symptom from this one.Suggested direction (platform-level)
Give the agent a liveness signal independent of "process up / HTTP 200", and recover on staleness. Options, roughly in order of effort:
/healthlike{"status":"ok","ticks":<n>,"last_progress_ms":<n>}). The agent polls it and restarts the cog if the counter hasn't advanced within a cog-declared interval./healthmust reflect work progress, not just thread liveness — so this failure mode is designed out rather than discovered in the field.(The cog-side half of this — making the
doomcog's own/healthreport frame liveness and self-exit on a stalled render loop so the supervisor can restart it cleanly — is being handled in cognitum-one/cogs#28. This issue tracks the platform-level gap that applies to every cog.)Steps to reproduce
doomcog reproduces it after long uptime; a worker thread blocking or wedging while the HTTP thread keeps serving is enough).GET /healthstill returns{"status":"ok"}, the process stays alive,GET /api/v1/appsshows the cog healthy — but the cog produces no new work.stop/start(orsystemctl restart).Logs / console output
(Cog-side stdout was unavailable — journald had rotated by the time it was noticed, which is itself part of the problem: there's no retained signal that the loop stalled.)
Confirmation