Scheduled Tasks

A scheduled Task is a reusable blueprint — a saved repo + prompt + agent configuration. Attach a trigger (cron schedule, webhook, or ticket event) and Optio spawns a fresh Task through the full pipeline on each firing: repo clone, setupCommands, secrets injection, PR tracking.

Info

Both Repo Tasks (ends in a PR) and Standalone Tasks (no repo, logs + side effects) can be scheduled. The difference surfaces as the type field on the unified /api/tasks endpoint — repo-blueprint vs standalone.

Common use cases

  • Daily CVE patching — run every morning, prompt the agent to update dependencies with security fixes and open a PR.
  • Scheduled dependency bumps — weekly pnpm/npm updates.
  • Webhook-driven tasks — external systems POST to trigger a fresh PR (e.g. a Sentry alert spawning a fix task).
  • Ticket-driven tasks — every ticket with label cve creates a task from a security-specialized blueprint.

From the dashboard

  1. Navigate to Tasks → New Task.
  2. At the top of the form, switch from Run now to Schedule.
  3. Fill in the usual repo + title + prompt + agent fields.
  4. Pick a trigger type (schedule, webhook, or ticket) and provide its config. For ticket triggers, choose the source (GitHub, Linear, Jira, or Notion) and optionally filter by labels.
  5. Click Create Schedule.

The task config is saved and visible at /tasks/scheduled. Each firing produces a regular Task at /tasks/:id with a taskConfigId link in its metadata.

API

All examples below use the unified /api/tasks resource with a type discriminator. Legacy paths (/api/task-configs, /api/jobs) still work as aliases.

Create a scheduled Repo Task

POST /api/tasks
POST /api/tasks
Content-Type: application/json

{
  "type": "repo-blueprint",
  "name": "Daily CVE patch",
  "title": "Patch security advisories",
  "prompt": "Check for security vulnerabilities in dependencies and open a PR with patches.",
  "repoUrl": "https://github.com/acme/web",
  "repoBranch": "main",
  "agentType": "claude-code",
  "priority": 50,
  "enabled": true
}

For a scheduled Standalone Task, use "type": "standalone" and omit repoUrl.

Add a schedule trigger

POST /api/tasks/:id/triggers
POST /api/tasks/${id}/triggers
Content-Type: application/json

{
  "type": "schedule",
  "config": { "cronExpression": "0 9 * * *" },
  "enabled": true
}

Cron expressions are five-field UTC (minute, hour, day, month, weekday). The worker polls every 60s by default (configure via OPTIO_WORKFLOW_TRIGGER_INTERVAL).

Add a webhook trigger

POST /api/tasks/:id/triggers
POST /api/tasks/${id}/triggers
Content-Type: application/json

{
  "type": "webhook",
  "config": { "path": "sentry-to-task" },
  "enabled": true
}

The webhook path is globally unique. Upstream POST to /api/hooks/sentry-to-task — the payload becomes the task's triggerParams metadata and is available to the prompt template.

Add a ticket trigger

POST /api/tasks/:id/triggers
POST /api/tasks/${id}/triggers
Content-Type: application/json

{
  "type": "ticket",
  "config": { "source": "github", "labels": ["cve"] },
  "enabled": true
}

Fires when the ticket-sync worker discovers a matching ticket. The ticket's source/externalId/title/body/labels/url are passed as params to the prompt.

Manually spawn a run

POST /api/tasks/:id/runs
POST /api/tasks/${id}/runs
Content-Type: application/json

{ "params": { "severity": "high" } }

Response:
{ "runId": "5b3ce588-fd6d-4682-a1c4-73f00d65ad24", "type": "repo-task" }

For blueprints (repo-blueprint) this calls instantiateTask(). For Standalone Tasks it creates a workflow_run.

Prompt templating

Title and prompt fields support {{param}} substitution and {{#if param}}...{{/if}} blocks. When a trigger fires, the trigger's payload (webhook body, ticket fields) is passed as params and substituted into the prompt. Link a named template from the template library via promptTemplateId to share a template across multiple task configs.

Pausing and running

Every task config has an enabled flag, and every trigger has its own enabled flag. Pausing the config stops all triggers from firing; pausing an individual trigger stops just that one. The /tasks/scheduled page and the per-config detail page at /tasks/scheduled/:id expose one-click pause/resume and manual-run controls.