Guides
Jobs
defineJob files under jobs/ run on a durable queue — hosted metadata Postgres, or reserved robodev_jobs tables in robodev dev.
Default-export defineJob from robodev/jobs/<name>.ts. The job name is the path after jobs/ without .ts. API, job, and Auth hook handlers enqueue with ctx.jobs.enqueue({ name, payload?, runAt?, delayMs? }). Optional cron is a 5-field UTC expression (minimum interval one minute), validated at deploy and at robodev dev activate. On each matching tick the runtime enqueues the job with payload null. Overlap piles up; missed ticks are not backfilled. Remove cron and save (or redeploy) to stop future ticks. The worker retries 5 times with backoff, then marks the run dead. Retry dead jobs from /projects/:id/jobs — that dashboard retry is hosted-only. Offline robodev dev uses reserved robodev_jobs tables on the local Postgres (not database.ts); a dead row stays dead until you enqueue again. Job context has db, email, llm, env, push, jobs, sockets, pdf, storage, and payload — no HTTP. See Storage (/docs/storage). llm, agent, and push throw offline (llm_not_configured / push_not_configured); sockets run locally; pdf hops to Starbase — see PDF (/docs/pdf); email prints to the terminal.
Deploy checks every jobs/**/*.ts file (and robodev dev checks it on reload). Each one must default-export defineJob(...), or the deploy fails with 400 invalid_job_definition naming the file — put shared helper modules in robodev/api/_lib.ts or robodev/api/_lib/** and import them from the job (see Helper files and bundling in /docs/api). A cron that doesn't parse, or never matches a time in the next 366 days (for example 0 0 31 2 *), fails with 400 invalid_job_cron naming the file and the expression; the previous deployment stays live. Cron errors name the field and its range (minute 0-59, hour 0-23, day-of-month 1-31, month 1-12, day-of-week 0-7); seconds are not supported, so every 5 minutes is */5 * * * *.
timeoutMs on defineApi, defineJob, defineSocket, and defineAuthHook must be a positive integer; 0, negative, fractional, NaN, or Infinity fail the deploy with 400 invalid_timeout. Leave it out for the 10000 ms default. Values above 30000 are clamped to 30000 and the deploy returns a warning (printed by robodev deploy and robodev dev).
ctx.jobs.enqueue limits: delayMs must be a finite number from 0 to 31536000000 (365 days), else 400 invalid_job_delay. runAt must be a valid Date no more than 365 days ahead, else 400 invalid_job_run_at; a past runAt runs right away, and runAt wins when both are set. An unknown or empty name is 400 unknown_job listing the known jobs. Uncaught in an API handler, these become that HTTP status instead of 500 handler_failed.
On /projects/:id/jobs, each job shows its next cron run, its last run, and the last cron tick. A tick that could not enqueue (for example the project could not start) shows Skipped with the error. Run now enqueues one run with payload null. In Logs, Cancel stops a queued run (status cancelled; it never runs and can't be retried), and clicking a run opens its payload, result, error, attempts, and duration. Attempts count every executed attempt, the successful one included (first-try success is 1/5). Retry resets attempts but keeps the previous error for reference.