qara is a lightweight daemon that monitors processes and sends real-time notifications to Telegram. Start a long-running job, close your laptop, and stay in control from your phone.
Built for ML practitioners who run multi-hour training jobs, but works with any process.
qara run python train.py --name "gpt-finetune"
# => Telegram: "Process 'gpt-finetune' started (PID 41592)"
# => ... hours later ...
# => Telegram: "Process 'gpt-finetune' finished (exit 0, 3h 42m)"- Spawn or attach — start a new process with
qara runor monitor an existing one withqara attach <pid> - Telegram notifications — get notified on start, finish, and crash with configurable stdout tail
- Remote control — send
/status,/kill,/history,/logsfrom Telegram - Daemon mode — runs as a user-level service (systemd, launchd) — no root required
- JSON output —
--format jsononstatusandhistoryfor scripting
# Install globally (recommended)
pipx install qara
# Or with uv
uv pip install qaraqara config initThis creates ~/.config/qara/config.toml. Open it and add your Telegram bot token and user ID:
[telegram]
bot_token = "123456:ABC-DEF..."
allowed_user_ids = [your_telegram_user_id]How to get a bot token and user ID
- Message @BotFather on Telegram and send
/newbot - Follow the prompts to name your bot — you'll receive a token like
123456:ABC-DEF... - To find your user ID, message @userinfobot and it will reply with your numeric ID
- Important: Send any message to your new bot first so it can message you back
qara daemon startqara run python train.py --name "experiment-1"That's it. You'll receive Telegram notifications when the process starts, finishes, or crashes.
# Run a process
qara run python train.py --name "my-job"
# Attach to an existing process
qara attach 12345 --name "background-job"
# List watched processes
qara status
qara status --format json
# View completed runs
qara history --last 10
qara history --format jsonOnce the daemon is running, send these commands to your bot:
| Command | Description |
|---|---|
/status |
List all watched processes |
/kill <name> |
Send SIGTERM to a process (escalates to SIGKILL) |
/history |
Show recent completed runs |
/logs <name> |
Get last N lines of stdout |
qara daemon start # Start in background
qara daemon start --foreground # Start in foreground (for systemd/launchd)
qara daemon stop # Stop the daemon
qara daemon status # Check if daemon is running# Auto-detects systemd (Linux) or launchd (macOS)
qara install
# Preview what would be installed
qara install --dry-run
# Remove the service
qara uninstallFull config.toml reference:
[daemon]
log_level = "INFO" # DEBUG, INFO, WARNING, ERROR
[telegram]
bot_token = "YOUR_BOT_TOKEN"
allowed_user_ids = [123456789]
[telegram.notifications]
on_start = true
on_finish = true
on_crash = true
stdout_tail_lines = 20 # lines of stdout included in finish notification
[commands]
enabled = ["status", "kill", "restart"]
kill_timeout_seconds = 10
[commands.allowed_scripts]
# alias = "/absolute/path/to/script.py"qara daemon start
│
▼
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Watcher │────▶│ EventEngine │────▶│ Telegram │
│ (per proc) │ │ (pub/sub) │ │ Channel │
└─────────────┘ └──────┬───────┘ └──────────────┘
│
┌──────┴───────┐
│ Plugins │
│ (GPU, loss) │
└──────────────┘
▲
│ IPC (Unix socket)
│
qara run / qara status / ...
- Watcher spawns or attaches to processes via
asyncio.create_subprocess_exec - EventEngine is sequential async pub/sub — handlers receive events in subscription order
- Plugins subscribe directly to the engine and receive all events including stdout/stderr lines
- NotificationBus filters internal events before routing to channels
- IPC uses newline-delimited JSON over Unix sockets (Linux/macOS)
git clone https://github.com/warptengood/qara
cd qara
uv sync --group dev
# Run checks
uv run ruff check src/ # lint
uv run ruff format src/ # format
uv run mypy src/ # type check
uv run pytest # testsSee CONTRIBUTING.md for guidelines.
MIT — see LICENSE for details.


