# Scheduler

The **Scheduler** runs a command on a recurring schedule - a Django management command, a cleanup, a nightly report - with no queue or worker involved. Find it under an app's **Processes → Scheduler**.

Each job becomes a cron entry on your server, run in the app's environment (its virtualenv, project directory and environment variables), exactly like a [background process](background-processes.md) or a deploy command. Every run's output is captured to the system journal so you can read it back per job.

## Adding a scheduled job

1. **Processes → Scheduler → Add scheduled job.**
2. Give it a **name** and the **command**. Bare tool names resolve in the app's virtualenv and the working directory is your project folder, so it reads the same as it would over SSH:

   ```
   python manage.py clearsessions
   ```

3. Set the **schedule**, two ways:
   - **Wizard** - pick a frequency (every minute, hourly, daily, weekly, monthly) and a time. Weekly adds a day-of-week, monthly a day-of-month.
   - **Cron** - write a raw 5-field cron expression, e.g. `0 3 * * *`.

   Either way a plain-English preview ("Every day at 03:00") confirms what you set before you save.

## Watching it run

Each job row shows its **last run** - "ran 3 minutes ago", or "failed (exit 1)" when the command exits non-zero. Alongside each job:

- **Run now** fires the command immediately, off-schedule, so you can test it without waiting for the next tick.
- **View output** shows recent runs' output, read straight from the server's journal.
- The **refresh** button in the header re-checks status.

## Scheduler or a background process?

- **Scheduler** - a command that runs, does its work and **exits**, on a schedule. Session cleanup, a nightly email, pruning old rows. Nothing to keep alive.
- **[Background processes](background-processes.md)** - a command that **stays running** (a queue worker, a bot). If you run Celery with a `beat` schedule declared in your code, that beat daemon is a background process, not a scheduled job.

Rule of thumb: if the command finishes on its own, schedule it here; if it's meant to stay up, it's a background process.

## Managing jobs from your repo

Jobs can also live in your repo's [pyvolt.toml](pyvolt-toml.md) under a
`[schedule]` section, versioned and deployed with your code:

```toml
[schedule]
cleanup = { command = "python manage.py clearsessions", cron = "0 3 * * *" }
```

When the section is present the file defines the complete set of jobs: this
tab goes read-only (Run now and View output keep working), and the next
deploy reconciles the server's cron entries to match the file exactly.

## Any command works

There's no tool lock-in. A scheduled job is just a command run on a schedule - a management command, a shell one-liner, a script in your repo. What it does lives in the command, not a setting.
