Operations — keeping TINY alive·
⏱ 6 min · every number below was measured on the CM4
The robot is a 4-core CM4 with 4 GB of RAM, a 14 GB SD card that is 89 % full and one process — Pollen's daemon — that owns every sensor and motor. Everything we run competes with it. This page is the operational memory of the first two days: what broke, why, how we saw it, and what to type.
The 60-second health check·
ssh reachy 'systemctl --user is-active tiny-wake tiny-tts tiny-voice tiny-telegram tiny-thinker reachy-dashboard reachy-tunnel; \
cut -d" " -f1-3 /proc/loadavg; df -h / | tail -1; \
P=$(pgrep -f "reachy_mini.daemon.app.main"); echo "daemon pid $P fds $(sudo ls /proc/$P/fd | wc -l) limit $(grep "Max open files" /proc/$P/limits | awk "{print \$4}")"'
curl -s https://reachy.cagatay.my/api/health | jq '{daemon: .daemon, camera: .camera.fps, pressure: .pressure, stream: .stream}'
Healthy on 2026-09-17: seven active, load 3–4, daemon ≈ 300–600 fds with limit 65536, camera 10 fps,
pressure.close_wait 0, stream.connected true at 10 Hz.
Reading the logs
journalctl --user says No journal files were found on the CM4 — but the system journal
does hold the user units. This works:
journalctl _SYSTEMD_USER_UNIT=tiny-voice.service --since "10 min ago" -o cat
journalctl -u reachy-mini-daemon --since "5 min ago" -o cat | grep -oE '"(GET|POST) [^ ?]*' | sort | uniq -c | sort -rn
The second line is the daemon's access log by endpoint — the fastest way to see who is hammering it.
Incident log·
| when (BST) | what you saw | root cause | fix |
|---|---|---|---|
| 09-16 ~23:00 | every persona: "Lost connection with the server" for 12 min after a daemon restart | SDK client object never reconnects; client._is_alive stays False |
tools/_reachy_common.get_mini() rebuilds a dead client |
| 09-17 03:05 | cockpit dark for 2 min | systemctl stop reachy-dashboard + TimeoutStopSec=8 SIGKILL; uvicorn waits on MJPEG clients, a stop job never auto-restarts |
timeout_graceful_shutdown=2; only ever restart |
| 09-17 05:29 | camera, tracking and DoA dead 10 min; daemon at 109 % CPU | daemon hit 1024/1024 fds: 614 sockets on :8000 in CLOSE-WAIT — dashboard made ~1300 req/min on fresh TCP connections, tiny-mhs crash-looped every 40 s releasing media |
one keep-alive session + one shared state WebSocket (≈160 req/min, 0 CLOSE-WAIT); LimitNOFILE=65536 drop-in; watchdog timer; mhs gated on its broker |
| 09-17 06:20–07:00 | daemon fds climbing ~1/s (556 → 879), load 6, voice persona "up" every 5–80 s | tiny-voice crash-looped on invalid_api_key; every attempt POSTed /api/media/release, each release rebuilt the daemon's GStreamer/WebRTC pipeline (21 releases, 18 acquires in 5 min) and leaked ~30 unnamed unix stream sockets per rebuild; the drop-in limit had not applied because the daemon predates it |
sudo prlimit --pid <daemon> --nofile=65536:524288 live (no restart); key replaced; voice_listener no longer releases media on retry and backs off exponentially (3fc0e23) |
The fourth row is the important lesson: a persona that cannot start must not touch the daemon's media.
Every /api/media/release costs the daemon a full pipeline rebuild — and, in 1.10.0, some sockets it never
gives back. The dashboard's ⚠ pill (fds > 60 % of the limit or ≥ 50 CLOSE-WAIT) is the early warning; the
daemon access log tells you who.
Live mitigations (no reboot)·
# daemon near its fd limit and you cannot restart it mid-demo
P=$(pgrep -f "reachy_mini.daemon.app.main"); sudo prlimit --pid $P --nofile=65536:524288
# a persona is crash-looping: stop it, read why, fix, restart
systemctl --user stop tiny-voice
journalctl _SYSTEMD_USER_UNIT=tiny-voice.service -n 40 -o cat
systemctl --user restart tiny-voice
# daemon lost the camera (every persona restart used to do this)
curl -s -X POST localhost:8000/api/media/acquire # the dashboard does this itself within ~1 s
# thinker gestures on top of a live demo
curl -s -X POST -H "Authorization: Bearer $REACHY_TOKEN" -H 'content-type: application/json' \
https://reachy.cagatay.my/api/control/demo -d '{"on":true}' # = systemctl --user stop tiny-thinker
# daemon truly wedged
sudo systemctl restart reachy-mini-daemon # personas reconnect on their own now
Budgets·
| resource | idle | with face tracking | with dashboard camera | comment |
|---|---|---|---|---|
| load average (4 cores) | ~2.5 | +1.0–1.5 | +0.5 | tracking is ON by default (REACHY_TRACK_AUTOSTART=1); turn it off (F) if the temperature pill goes amber |
| daemon CPU | ~60 % | ~135 % | — | YuNet runs inside the daemon |
| daemon fds | 300–600 | — | — | +≈30 leaked per media release/acquire cycle |
| requests to :8000 | ≈160/min | — | — | face poll 2 Hz is the largest single caller (541 per 5 min) |
| disk | 89 % | 1.5 GB free — no new venvs, apt clean before anything |
Before a demo·
POST /api/control/demo {"on":true}(orDin the cockpit) — thinker off.- Health check above;
pressure.fdsunder ~800 or raise the limit live. - Face tracking on, turn-to-sound on (🔊 pill) — both default on.
- Phone: stop Telegram banners — that is the thinker; step 1 covers it.
- Volume: say "TINY, normal volume" or
POST /api/volume {"level":60}. - After:
demo off— the heartbeat is what keeps the Telegram photo stream alive.
What we never do·
git pushfrom the robot or copy.envanywhere — the repo is public.- Expose
:8000— the daemon has no auth. systemctl stopthe dashboard during a demo —restart.- Poll the daemon from new code — read
robot.streaminside the dashboard, or/api/statefrom outside. - Prune, purge or
pip installon the CM4 withoutdf -hfirst.