python-daemon-pattern.md 4.7 KB

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
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:

# 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 <job_id>
  • 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 <id> 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.

  • 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)