Background Jobs

Connect a Trigger.dev project for automations and long agent runs, deploy the Mogplex tasks, and schedule the cron routes.

Mogplex runs long and scheduled work outside the web request cycle. Two pieces do this work:

  • Trigger.dev tasks (trigger/): automations, Slack runs, agent runs, model syncs, billing reconciliation, and scheduled cleanup.
  • Cron routes (/api/cron/*): small HTTP endpoints that repair and reap stuck jobs and sandboxes.

Trigger.dev

Use a project on Trigger.dev Cloud, or self-host Trigger.dev. The self-hosted stack includes a webapp, Postgres, Redis, ClickHouse, object storage, a container registry, and worker nodes.

1. Configure the app

TRIGGER_PROJECT_REF=proj_xxxxxxxxxxxxxxxxxxxx
TRIGGER_SECRET_KEY=tr_prod_...

Always set TRIGGER_PROJECT_REF

pnpm trigger:deploy and pnpm trigger:dev stop with setup guidance when TRIGGER_PROJECT_REF is unset, unless you pass --project-ref. Without that guard, the deploy would target the hosted Mogplex project.

Self-hosted Trigger.dev

For your own Trigger.dev v4 instance, set its URL in the app's environment and in .env.local:

TRIGGER_API_URL=https://trigger.example.com
  • The Trigger.dev SDK reads TRIGGER_API_URL, so every task the app starts goes to your instance.
  • The pnpm trigger:* wrapper passes --api-url to the Trigger.dev CLI for deploy, dev, login, and the other commands that accept it. An explicit --api-url argument wins.
  • An instance without a remote build server builds the worker image locally. The deploy machine needs Docker with buildx and a docker login to the registry your instance pulls from. Pass extra CLI flags after --, for example pnpm trigger:deploy -- --local-build.

2. Deploy the tasks

pnpm trigger:deploy

The wrapper loads .env and .env.local, plus any comma-separated files you list in TRIGGER_CLI_ENV_FILES. Then it runs the Trigger.dev CLI version that matches the installed SDK. Authenticate the CLI first with pnpm exec tsx scripts/trigger-cli.ts login, or set TRIGGER_ACCESS_TOKEN in CI.

Deploy the tasks every time you deploy the app, from the same commit. Tasks and the app share code in lib/, and mismatched versions cause hard-to-trace failures.

For local development, pnpm trigger:dev runs the tasks against your development environment.

3. Give the tasks their environment

Tasks import the same server code as the app. They need the same server environment: database URLs, AI keys, CRON_SECRET, Sentry DSNs, and the GitHub and Slack credentials.

  • On Vercel: when a Vercel token and project ID are available at deploy time (VERCEL_ACCESS_TOKEN or PLATFORM_VERCEL_TOKEN, plus VERCEL_PROJECT_ID or PLATFORM_VERCEL_PROJECT_ID, and optionally VERCEL_TEAM_ID), the deploy syncs the Vercel project's env vars into Trigger.dev.
  • Otherwise: copy the server variables into the Trigger.dev project's environment variables by hand. Update them there whenever you change them in the app.

Task runtime

trigger.config.ts sets Node 22, a maximum run duration of 30 minutes, and one attempt per task. Scheduled tasks register themselves on deploy:

TaskSchedule (UTC)
sync-models0 6,18 * * *
sandbox-reaperevery 5 minutes
zombie-row-reaperevery 5 minutes
reconcile-ai-call-costsevery 10 minutes
vercel-link-reconciliationevery 15 minutes
reconcile-sandbox-billingevery minute
ai-spend-divergence-check0 5 * * *
sandbox-baseline-snapshot-reaper0 5 * * *
grant-annual-included-usage30 6 * * *
slack-tool-execution-retention17 * * * *

Cron routes

Each cron route accepts GET with Authorization: Bearer <CRON_SECRET>. If CRON_SECRET is unset, the route returns 503 CRON_SECRET_NOT_CONFIGURED. A wrong token returns 401.

RouteSchedule
/api/cron/sync-models0 6 * * *
/api/cron/sandbox-reaper*/5 * * * *
/api/cron/repair-jobs*/5 * * * *
/api/cron/reconcile-job-runs*/5 * * * *
/api/cron/zombie-row-reaper*/5 * * * *

On Vercel, vercel.json schedules these routes and sends the secret for you.

With Docker Compose, the cron service calls these routes on schedule.

Elsewhere, run the same scheduler as a long-lived process:

CRON_SECRET=... CRON_TARGET_URL=https://<domain> node scripts/self-host-cron.mjs

Or schedule the routes in your own cron. Example crontab:

*/5 * * * * curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://<domain>/api/cron/sandbox-reaper
*/5 * * * * curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://<domain>/api/cron/repair-jobs
*/5 * * * * curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://<domain>/api/cron/reconcile-job-runs
*/5 * * * * curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://<domain>/api/cron/zombie-row-reaper
0 6 * * *   curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://<domain>/api/cron/sync-models

Without these routes, stuck jobs are not repaired and idle sandboxes keep running and billing.

Edit on GitHub

On this page