Optio's runtime tier has three flavors:
- Repo Task — agent runs in a worktree, opens a PR, terminates.
- Standalone Task — agent runs once with no repo, produces side effects, terminates.
- Persistent Agent — long-lived, named, message-driven; doesn't terminate. See persistent-agents.md.
The first two share most of the pipeline (prompts, triggers, agent config, run history, logs, costs) — they're separated only by whether a repo is attached. Persistent Agents are a distinct runtime model: turns are inputs to a long-lived process, rather than the unit of work itself.
This guide covers the two Task flavors, when to use each, and how the unified /api/tasks HTTP layer presents them. For Persistent Agents see persistent-agents.md.
A Repo Task targets a specific repository and ends by opening a pull request. The pipeline:
- Find or spin up an isolated Kubernetes pod for the repo (pod-per-repo).
- Create a git worktree for the task — multiple tasks can run concurrently on one pod.
- Run the configured agent (Claude Code, Codex, Copilot, Gemini, OpenCode) with the rendered prompt.
- Stream structured logs back to the web UI in real time.
- The agent stops after opening a PR — it does not block on CI.
- The PR watcher tracks CI checks, review status, and merge state.
- If enabled, auto-trigger the code-review agent on CI pass or PR open.
- If enabled, auto-resume the agent when reviewers request changes.
- Auto-complete on merge; auto-fail on close.
Use a Repo Task when the work produces code: bug fixes, features, refactors, dependency bumps, doc edits — anything that wants to land as a reviewed PR.
A Standalone Task runs an agent in an isolated pod with no repo checkout. It produces logs and side effects: querying Slack, writing to a database, posting a report, calling an MCP server, triaging a ticket queue.
Use a Standalone Task when the work is not code: scheduled reports, cron-driven triage, webhook responses, on-demand operational scripts.
The user-facing flavors are backed by three internal types. The distinction is whether the row is a blueprint (definition that spawns runs) or a one-off run:
| Type | Backing table | Purpose | Has runs | Has triggers |
|---|---|---|---|---|
repo-task |
tasks |
Ad-hoc one-time Repo Task | No (it is a run) | No |
repo-blueprint |
task_configs |
Reusable Repo Task definition that spawns tasks |
Yes | Yes |
standalone |
workflows |
Standalone Task definition that spawns workflow_runs |
Yes | Yes |
A blueprint plus a fired trigger produces a run. The same trigger types (manual, schedule, webhook, ticket) work for both repo-blueprint and standalone because the trigger table is polymorphic — see Triggers below.
Backend-naming note. The schema still says
workflows,workflow_runs, andworkflow_triggersfor historical reasons, and the user-facing label for Standalone Tasks settled on Jobs (which matches the existing/api/jobsURL and the/jobs/*web routes)./api/tasksis the canonical polymorphic surface;/api/jobs/*and/api/task-configs/*are back-compat aliases.
All three types are reachable through one polymorphic resource. The server resolves an ID across all three tables; UUIDs are globally unique so there is no collision.
| Endpoint | Purpose |
|---|---|
GET /api/tasks?type=repo-task|repo-blueprint|standalone|all |
Unified list, filterable by type |
POST /api/tasks |
Create. Body takes { type, ... } and dispatches to the right service |
GET /api/tasks/:id |
Resolve across tables; returns the native row tagged with type |
GET/POST /api/tasks/:id/runs[/:runId] |
List/start runs (spawned tasks for blueprints, workflow_runs for standalone, 405 for ad-hoc) |
GET/POST/PATCH/DELETE /api/tasks/:id/triggers[/:triggerId] |
Manage triggers (405 for ad-hoc repo-task) |
The resolver lives in apps/api/src/services/unified-task-service.ts (resolveAnyTaskById) and checks tasks → task_configs → workflows in order. The polymorphic routes are in apps/api/src/routes/tasks-unified.ts.
Legacy /api/jobs/* and /api/task-configs/* endpoints still work as thin aliases.
The web UI hides the schema split entirely. As of v0.4 the old /tasks-with-tabs hub is gone — each surface lives at its own top-level route, grouped under Run in the sidebar. User-facing names: Standalone Tasks are called Jobs, PR reviews are called Reviews.
/tasks— Repo Tasks list. Ad-hoc and blueprint-spawnedtasksruns, with bulk actions, state filters, and real-time WebSocket updates./jobs,/jobs/:id,/jobs/:id/runs/:runId— Jobs (Standalone Tasks): blueprint list, detail, and per-run views. One-click "Run Now."/reviews,/reviews/:id— Reviews: code-review subtasks plus external PR reviews, with CI / review / merge tracking./issues— GitHub Issues across connected repos. "Assign to Optio" creates an ad-hoc Repo Task./tasks/scheduled— managerepo-blueprintrows: schedule, webhook, ticket, manual triggers; pause/resume; run-now./agents,/agents/:id— Persistent Agents (the third tier — see persistent-agents.md). Listed under Live in the sidebar alongside/sessions.
Legacy bookmarks like /tasks?tab=standalone, /tasks?tab=issues, and /tasks?tab=prs are redirected to /jobs, /issues, and /reviews respectively.
Triggers live in one polymorphic table, workflow_triggers, keyed by (target_type, target_id). target_type is "job" (Standalone) or "task_config" (Repo blueprint). Trigger types: manual, schedule (cron), webhook, ticket.
The workflow-trigger-worker polls due schedule triggers every 60 seconds (OPTIO_WORKFLOW_TRIGGER_INTERVAL) and dispatches:
target_type="job"→workflowService.createWorkflowRun()→ spawns aworkflow_runsrow.target_type="task_config"→taskConfigService.instantiateTask()→ renders the blueprint's prompt with trigger params, creates atasksrow, transitions it toqueued, and enqueues the BullMQ job.
Both blueprint types use the same template engine: {{param}} substitution and {{#if param}}...{{/if}} blocks. Templates render lazily at trigger-firing time, so values from the trigger payload (cron-formatted timestamps, webhook bodies, ticket fields) substitute into the prompt before the agent ever sees it.
Reusable templates live in prompt_templates with a kind discriminator (prompt / review / job / task). Template precedence: repo override → global default → hardcoded fallback.
| Concern | Service | Routes |
|---|---|---|
| Polymorphic resolution | services/unified-task-service.ts |
routes/tasks-unified.ts |
| Repo Task runs | services/task-service.ts, workers/task-worker.ts |
routes/tasks.ts |
| Repo Task blueprints | services/task-config-service.ts |
routes/task-configs.ts |
| Standalone Tasks | services/workflow-service.ts, workers/workflow-worker.ts |
routes/workflows.ts (also mounted as /api/jobs) |
| Triggers | services/workflow-trigger-service.ts, workers/workflow-trigger-worker.ts |
included in the above |
| Templates | services/prompt-template-service.ts |
routes/prompt-templates.ts |
State changes for both Repo Task runs and Standalone runs flow through the reconciliation control plane when enabled.