Skip to content

Latest commit

 

History

History
106 lines (70 loc) · 9.16 KB

File metadata and controls

106 lines (70 loc) · 9.16 KB

Tasks: Repo Tasks, Standalone Tasks, and Persistent Agents

Optio's runtime tier has three flavors:

  1. Repo Task — agent runs in a worktree, opens a PR, terminates.
  2. Standalone Task — agent runs once with no repo, produces side effects, terminates.
  3. 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.

The two flavors

Repo Task — has a repo

A Repo Task targets a specific repository and ends by opening a pull request. The pipeline:

  1. Find or spin up an isolated Kubernetes pod for the repo (pod-per-repo).
  2. Create a git worktree for the task — multiple tasks can run concurrently on one pod.
  3. Run the configured agent (Claude Code, Codex, Copilot, Gemini, OpenCode) with the rendered prompt.
  4. Stream structured logs back to the web UI in real time.
  5. The agent stops after opening a PR — it does not block on CI.
  6. The PR watcher tracks CI checks, review status, and merge state.
  7. If enabled, auto-trigger the code-review agent on CI pass or PR open.
  8. If enabled, auto-resume the agent when reviewers request changes.
  9. 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.

Standalone Task — no repo

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.

Three task types in the data model

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, and workflow_triggers for historical reasons, and the user-facing label for Standalone Tasks settled on Jobs (which matches the existing /api/jobs URL and the /jobs/* web routes). /api/tasks is the canonical polymorphic surface; /api/jobs/* and /api/task-configs/* are back-compat aliases.

The unified /api/tasks HTTP layer

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 taskstask_configsworkflows 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.

How they appear in the UI

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-spawned tasks runs, 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 — manage repo-blueprint rows: 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

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 a workflow_runs row.
  • target_type="task_config"taskConfigService.instantiateTask() → renders the blueprint's prompt with trigger params, creates a tasks row, transitions it to queued, and enqueues the BullMQ job.

Templates and parameters

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.

Service map

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.