Skip to content

Background daemon mode: gaia deals watch as persistent service #488

@kovtcharov

Description

@kovtcharov

Summary

Implement gaia deals watch as a proper background daemon that persists across terminal sessions, with PID management, log rotation, and graceful shutdown — following the existing gaia api start --background pattern.

Motivation

The scheduled price checker needs to run continuously even after the user closes their terminal. The API server already has this pattern (gaia api start --background with PID file + log file). The DealAgent daemon reuses this pattern.

Design

Daemon Lifecycle

gaia deals watch                 # Start daemon (default 6h interval)
gaia deals watch --interval 2h   # Start with custom interval
gaia deals watch --status        # Show daemon status + next check time
gaia deals watch --stop          # Graceful shutdown
gaia deals watch --logs          # Tail the daemon log
gaia deals watch --logs -n 50    # Show last 50 log lines

Implementation (follows API server pattern)

# src/gaia/agents/deals/daemon.py
import subprocess
import os
import signal

PIDFILE = os.path.expanduser("~/.gaia/deals/deals_watch.pid")
LOGFILE = os.path.expanduser("~/.gaia/deals/deals_watch.log")

def start_daemon(interval_hours: float = 6.0):
    """Start price watcher as background process."""
    if is_running():
        print(f"Deal watcher already running (PID {get_pid()})")
        return

    cmd = [sys.executable, "-m", "gaia.agents.deals.watcher",
           "--interval", str(interval_hours)]
    proc = subprocess.Popen(
        cmd, stdout=open(LOGFILE, "a"), stderr=subprocess.STDOUT,
        start_new_session=True
    )
    with open(PIDFILE, "w") as f:
        f.write(str(proc.pid))
    print(f"Deal watcher started (PID {proc.pid}, interval {interval_hours}h)")
    print(f"Logs: {LOGFILE}")

def stop_daemon():
    """Gracefully stop the daemon."""
    pid = get_pid()
    if pid and is_running():
        os.kill(pid, signal.SIGTERM)
        os.remove(PIDFILE)
        print(f"Deal watcher stopped (PID {pid})")

def get_status() -> Dict:
    """Return daemon status: running, PID, next check, items tracked."""

Log Rotation

  • Max log size: 10MB
  • Keep 3 rotated files
  • Uses logging.handlers.RotatingFileHandler

Startup Integration

Optionally auto-start on gaia deals if watchlist is non-empty:

You have 5 products on your watchlist. Start background monitoring? [Y/n]

Acceptance Criteria

  • gaia deals watch starts background process with PID file
  • gaia deals watch --stop gracefully terminates daemon
  • gaia deals watch --status shows running state, PID, next check, item count
  • gaia deals watch --logs tails the log file
  • Daemon survives terminal close (detached process)
  • Log rotation prevents unbounded disk usage
  • PID file cleaned up on shutdown and stale PID detection on startup
  • Windows-compatible (no POSIX-only signals)

Phase

Phase 2 — Tracking & Alerts

Dependencies

  • Scheduled price checking (Phase 2)
  • Follows gaia api start --background pattern from src/gaia/api/app.py

Metadata

Metadata

Assignees

No one assigned

    Labels

    agentcliCLI changesdealsDealAgent: price tracking and deal discoveryenhancementNew feature or requestp2low priority

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions