VisualAgent Daemon Lifecycle and Reliability
Background
The VisualAgent daemon (visual_agent_daemon.sh) is a shell script that runs the executor in a loop, checking for new commands every few seconds. However, it has a known reliability issue: the daemon process can exit silently while the PID file remains, creating a "zombie" state where the system thinks the daemon is running but it's actually dead.
The Problem
Symptom: Daemon Appears Running But Isn't
# PID file shows a running process
su -c "cat /sdcard/VisualAgent/daemon.pid"
# Output: 1093
# But ps shows no such process
su -c "ps | grep 1093 | grep -v grep"
# Output: (nothing)
# Or the process exists but is not the daemon
su -c "ps | grep 'sh' | grep -v grep"
# Output: root 1093 1 ... S sh
# But this is just a shell, not the daemon loop
Root Cause
The daemon script typically uses this pattern:
nohup sh "$SCRIPT_DIR/visual_agent_executor.sh" --daemon > "$LOG_FILE" 2>&1 &
echo $! > "$PID_FILE"
The executor script's --daemon mode runs:
while true; do
sleep 5
# process commands...
done
Issues that cause silent exit:
- Shell exits on error — if
set -eis in effect, any failed command kills the loop - Sleep returns non-zero — in some Android shells,
sleepcan return an error signal - Resource exhaustion — the loop may accumulate file descriptors or memory
- SIGTERM/SIGHUP — the process may receive a termination signal
- The
shprocess is just the parent — the actual daemon loop may be a child that exited
Health Check Procedure
Step 1: Check PID File
su -c "cat /sdcard/VisualAgent/daemon.pid 2>/dev/null || echo 'NO_PID_FILE'"
Step 2: Verify Process Exists
PID=$(su -c "cat /sdcard/VisualAgent/daemon.pid 2>/dev/null")
if [ -n "$PID" ]; then
su -c "ps -p $PID 2>/dev/null | grep -v PID || echo 'PROCESS_NOT_FOUND'"
fi
Step 3: Check Process Command Line
PID=$(su -c "cat /sdcard/VisualAgent/daemon.pid 2>/dev/null")
if [ -n "$PID" ]; then
su -c "cat /proc/$PID/cmdline 2>/dev/null | tr '\0' ' ' || echo 'NO_CMDLINE'"
fi
A healthy daemon should show something like:
sh /data/data/com.termux/files/home/visual_agent_executor.sh --daemon
Step 4: Check Log Activity
su -c "tail -10 /sdcard/VisualAgent/daemon.log 2>/dev/null || echo 'NO_LOG'"
A healthy daemon should show recent timestamps (within the last few seconds if the loop is running).
Step 5: Check for Command Processing
Create a test command and see if it's processed within 10 seconds:
# Create test command
su -c "printf '%s\n' '{\"type\":\"screenshot\",\"id\":\"daemon_health_check\"}' > /sdcard/VisualAgent/commands/daemon_health_check.json"
# Wait 10 seconds
sleep 10
# Check if processed
su -c "ls -la /sdcard/VisualAgent/processed/ | grep daemon_health_check || echo 'NOT_PROCESSED'"
Restart Procedure
If the daemon is dead or unhealthy:
Step 1: Stop (Clean Up PID File)
su -c "sh /data/data/com.termux/files/home/visual_agent_daemon.sh stop 2>/dev/null || rm -f /sdcard/VisualAgent/daemon.pid"
Step 2: Start Daemon
su -c "sh /data/data/com.termux/files/home/visual_agent_daemon.sh start"
Step 3: Verify Health
sleep 2
PID=$(su -c "cat /sdcard/VisualAgent/daemon.pid 2>/dev/null")
if [ -n "$PID" ]; then
su -c "ps -p $PID 2>/dev/null | grep -v PID || echo 'STILL_NOT_RUNNING'"
fi
Alternative: Use Cron Instead of Daemon
If the daemon is unreliable, consider using a cron job instead:
# Edit crontab
crontab -e
# Add lines (every 30 seconds)
* * * * * sh /data/data/com.termux/files/home/visual_agent_executor.sh
* * * * * sleep 30; sh /data/data/com.termux/files/home/visual_agent_executor.sh
Or use Hermes scheduled cron jobs to run the executor periodically.
Alternative: Use Single-Shot Execution
For batch processing, run the executor once and exit:
sh /data/data/com.termux/files/home/visual_agent_executor.sh
This is the most reliable approach for intermittent command processing.
Daemon Log Analysis
The daemon log (/sdcard/VisualAgent/daemon.log) reveals the daemon's state:
Healthy Daemon Log
[Thu Jun 25 20:49:59 CST 2026] 处理完成
[Thu Jun 25 20:49:59 CST 2026] 守护模式运行,每 5 秒检查一次...
[Thu Jun 25 20:52:09 CST 2026] 处理命令: test_cron_screenshot.json
[Thu Jun 25 20:52:10 CST 2026] 结果: {"status":"success",...}
[Thu Jun 25 20:52:11 CST 2026] 已移动到: /sdcard/VisualAgent/processed/test_cron_screenshot.json
Note: Timestamps should be recent (within loop interval + processing time).
Dead Daemon Log
[Thu Jun 25 20:49:59 CST 2026] 处理完成
[Thu Jun 25 20:49:59 CST 2026] 守护模式运行,每 5 秒检查一次...
Note: No new entries after the "守护模式运行" line — the daemon exited right after logging this.
Prevention: Robust Daemon Implementation
If implementing a new daemon, use these patterns:
1. Trap Signals
trap 'echo "Daemon exiting"; rm -f "$PID_FILE"; exit 0' TERM INT HUP
2. Use exec for the Loop
exec sh -c '
while true; do
sleep 5
# process commands...
done
'
3. Log Before and After Sleep
while true; do
echo "[$(date)] Checking for commands..."
# process commands...
echo "[$(date)] Sleeping..."
sleep 5
done
4. Use flock for PID File
exec 200>"$PID_FILE"
flock -n 200 || { echo "Daemon already running"; exit 1; }
echo $$ >"$PID_FILE"
Quick Reference: Daemon Commands
| Action | Command |
|---|---|
| Check status | su -c "sh ~/visual_agent_daemon.sh status" |
| Start daemon | su -c "sh ~/visual_agent_daemon.sh start" |
| Stop daemon | su -c "sh ~/visual_agent_daemon.sh stop" |
| Check PID | su -c "cat /sdcard/VisualAgent/daemon.pid" |
| Verify process | su -c "ps -p \$(cat /sdcard/VisualAgent/daemon.pid)" |
| View log | su -c "tail -20 /sdcard/VisualAgent/daemon.log" |
| Force restart | su -c "rm -f /sdcard/VisualAgent/daemon.pid; sh ~/visual_agent_daemon.sh start" |
Related References
pre-existing-visualagent-infrastructure.md— Discovery checklist for existing VisualAgent installationscron-polling-anti-patterns.md— Correct polling behavior when using cron instead of daemonvisual-agent-service-patterns.md— General VisualAgent service patterns