# Python Daemon Pattern for VisualAgent ## Session: 2026-06-27 — Python daemon replaces shell daemon, eliminates all shell-origin bugs ## Why Python Instead of Shell Shell-based VisualAgent daemons (`visualagent_daemon.sh`, `visual_agent_service.sh`) suffer from multiple failure modes that compound over time: | Problem | Shell Cause | Python Fix | |---------|------------|------------| | `python3` not in root `$PATH` | Shell inherits PATH; root context loses Termux paths | Full path in shebang: `#!/data/data/com.termux/files/usr/bin/python3` | | JSON quoting breaks in `su -c "echo '...'"` | Nested single/double quotes explode | `json.load()` / `json.dump()` — no quoting needed | | `grep` parsing fragile on Android toybox | `grep -o '"type"...' | cut -d'"' -f4` misses edge cases | `json.load()` always correct | | Daemon dies when `su` session exits | `su -c 'nohup ... &'` then su exits → SIGHUP | Started via Hermes `terminal(background=true)` with `su -c 'exec ...'` | | Code version mismatch | Shell reads script line-by-line into memory; edits to file don't affect running process | Same issue exists, but Python's single-file design and clear versioning (v1, v2, v3) makes it easier to track | | Race condition: multiple instances write same result file | Each cron invocation starts a new shell daemon | Single Python process with `os.rename()` (atomic on same fs) | ## The Correct Startup Pattern The ONLY reliable way to start a long-lived daemon on Android from Hermes: ```python # In Hermes: terminal(background=true, command='su -c "exec /data/data/com.termux/files/usr/bin/python3 /data/data/com.termux/files/home/va_daemon.py"') ``` **Why this works:** - `terminal(background=true)` — Hermes keeps a persistent session alive as the daemon's parent - `su -c 'exec ...'` — `exec` replaces the su shell process with Python, so there's no intermediate shell to SIGHUP - Full Python path — avoids PATH resolution issues in root context **What does NOT work (and why):** | Attempt | Failure Mode | |---------|-------------| | `su -c 'nohup ./daemon.sh &'` | `su` session exits → child killed | | `su -c 'setsid ./daemon.sh &'` | Same — `su` exiting kills the session | | `su -c './daemon.sh &'` (foreground) | Hermes rejects `&` in foreground mode | | `bash daemon.sh start` (no `su`) | Daemon can't run `screencap`, `input`, `monkey` without root | ## Cron Job Competing Instances A cron job running `*/5 * * * *` that spawns Hermes agents to "monitor VisualAgent" creates a cascading problem: 1. Cron fires → new Hermes agent starts 2. That agent starts a daemon (shell or Python) 3. 5 minutes later, cron fires again → another Hermes agent + another daemon 4. Multiple daemons now compete for the same command files 5. Result files get concatenated JSON from both daemons 6. Log fills with interleaved messages from both **Solution:** - If a persistent Python daemon is running, **pause the cron job**: `hermes cron pause ` - The daemon handles monitoring; the cron job is redundant and harmful - See pitfall #68 (below) ## Deployment Checklist When deploying a new VisualAgent daemon: 1. **Kill all old daemons**: `su -c "ps -ef | grep -i visual | awk '{print \$2}' | xargs kill -9"` 2. **Disable old scripts**: `mv visualagent_daemon.sh .bak_visualagent_daemon.sh` (prevents accidental invocation) 3. **Pause competing cron**: `hermes cron pause ` if a cron job also monitors commands 4. **Deploy `va_daemon.py`**: Copy to device, ensure shebang points to correct Python 5. **Start via Hermes background**: Use the correct startup pattern above 6. **Verify with a test command**: `echo '{"command":"screenshot"}' > /sdcard/VisualAgent/commands/test.json` 7. **Check result file**: `cat /sdcard/VisualAgent/results/result_test_*.json` should show `{"status": "success", ...}` with spaces (Python `json.dump` format, not compact shell `echo` format) ## Distinguishing Python vs Shell Daemon Output | Signal | Python Daemon | Shell Daemon | |--------|--------------|-------------| | JSON spacing | `{"status": "success", ...}` (spaces after colons) | `{"status":"success",...}` (compact, no spaces) | | Log format | `done: filename.json (success)` | `结果已保存:` / `已处理:` / `未知命令:` (Chinese) | | Error messages | English: `unknown cmd: ...`, `json parse error: ...` | Chinese: `未知命令:`, `无法解析命令类型` | If you see BOTH formats in the same result file or log, there are TWO daemon instances running — kill the old one. ## Related Pitfalls - Pitfall #23: Daemon lifecycle and stale PID files - Pitfall #39: Pre-existing watchers causing file disappearance - Pitfall #68: `su -c` sessions kill child processes on exit - Pitfall #69: Daemon code version mismatch (process has old code in memory)