Workflows

June 14, 2026 · View on GitHub

Use workflows when a repo needs one canonical operational path instead of a loose pile of tasks.

The short version:

  • tasks are execution primitives
  • checks are named readiness assertions
  • readiness.probes are reusable transport-level readiness definitions
  • surfaces are reusable runtime endpoint definitions
  • services are dependencies
  • workflows are the repo's intended operational paths

If ota.yaml is the repo contract, workflows is where the contract says:

this is the way this repo is meant to become useful

Why workflows exist

Serious repos usually do not have one flat definition of "ready".

A repo may have:

  • contributor setup readiness
  • backend development readiness
  • frontend development readiness
  • full app readiness
  • test readiness
  • CI readiness

If ota only had tasks, operators and agents would have to guess which task is the actual front door.

Workflows solve that by turning low-level contract pieces into one canonical operational path.

What a workflow is

A workflow is a named path built from existing contract primitives:

  • an optional setup task
  • an optional run task
  • optional required services
  • optional readiness checks
  • optional readiness surfaces
  • optional exposed endpoints

Example:

readiness:
  probes:
    app-ready:
      kind: http
      url: http://127.0.0.1:5678/healthz/readiness
      success:
        status: [200]
      timeout: 10000

workflows:
  default: app
  app:
    intent: local_development
    description: Canonical local app workflow
    setup:
      task: setup
    run:
      task: dev
    services:
      required:
        - postgres
    readiness:
      probes:
        - app-ready
      surfaces:
        - backend
    exposes:
      - surface: backend

This does not replace tasks, checks, or services. It composes them into a canonical path.

When to use workflows

Add workflows when at least one of these is true:

  • the repo has more than one valid development path
  • setup and run are not the same thing
  • humans keep asking "what should I run first?"
  • ota up should target a specific app or service path, not generic repo setup
  • the repo has backend/frontend or app/worker splits
  • agent automation should optimize for a narrower path than the human default

You do not need workflows for every repo.

For a small library with one obvious setup and one obvious test, plain tasks may be enough.

When not to use workflows

Do not add workflows just to rename tasks.

Bad reasons:

  • the repo only has one trivial path
  • the workflow adds no new operational meaning
  • the workflow just duplicates task names without clarifying readiness

If the contract still answers the same questions without workflows, keep it simpler.

How to choose the default workflow

workflows.default should name the repo's canonical operator path.

Choose the path that best answers:

  • what should a new contributor run first?
  • what should ota doctor and ota up optimize for by default?
  • what path best represents the repo's main selfish utility?

That default should be the human-facing repo front door.

It does not have to be the narrowest or cheapest path.

Example:

  • repo default workflow: full local app development
  • agent default task: backend-only dev server

That split is valid when the human default is broader but the agent-safe runtime should stay narrower.

How workflows interact with commands

Current command behavior:

  • ota doctor diagnoses the default workflow by default
  • ota check checks the default workflow by default
  • ota up prepares the default workflow by default
  • ota execution plan resolves the default workflow by default
  • --workflow <name> selects another declared workflow explicitly

In practice this means a repo can expose:

  • one canonical default path
  • several secondary paths for backend, frontend, AI, runtime, or CI work

without forcing humans or agents to infer that from task names alone.

How workflows relate to tasks

Tasks remain the execution layer.

Use tasks for:

  • commands you actually run
  • dependency chains
  • runtime declarations
  • execution contexts

Use workflows for:

  • repo-level intent
  • choosing the canonical prepare/setup/run path
  • grouping readiness under one named operational target

Use prepare for one explicit host-side bootstrap action before setup, such as creating .env.local from .env.example when the real setup path should still stay container-backed or otherwise non-host. It must stay on one native finite host-bootstrap owner: either a reusable prepare.task or an inline deterministic prepare.action, not an ordinary runtime task.

Prepare vs setup vs run:

  • prepare is explicit host bootstrap before setup
  • setup is repo preparation
  • run is the primary operational path

When the run path needs workflow-local dotenv input, keep that ownership on the run task with tasks.<name>.env_files. Use workflow prepare.task when the bootstrap deserves its own reusable task identity, and use inline prepare.action when the workflow itself honestly owns one finite deterministic bootstrap action or bundle before setup.

Good workflow design keeps those three meanings separate.

Good boundary:

  • tasks.setup installs dependencies
  • tasks.dev starts the app
  • checks.app-health probes the app
  • surfaces.backend defines the reusable app endpoint
  • workflows.app says those belong to the same operational path

How workflows relate to surfaces

Use surfaces when the same endpoint meaning should stay shared across tasks and workflows.

Example:

surfaces:
  backend:
    kind: http
    port: 5678
    path: /
    readiness:
      kind: http
      path: /healthz/readiness
      timeout: 10000

tasks:
  dev:
    run: pnpm dev
    runtime:
      kind: service
      surfaces:
        - backend
  backend:
    run: pnpm dev:backend
    runtime:
      kind: service
      surfaces:
        - backend

workflows:
  default: app
  app:
    run:
      task: dev
    readiness:
      surfaces:
        - backend
    exposes:
      - surface: backend
  backend:
    run:
      task: backend
    readiness:
      surfaces:
        - backend
    exposes:
      - surface: backend

That means:

  • the surface owns reusable endpoint meaning
  • task attachment makes the endpoint operational
  • workflow readiness selects which attached surfaces prove that workflow path
  • workflow exposes can resolve attached surface URLs without repeating literals

For the full operator guide to runtime surfaces, see surfaces.md.

How workflows relate to agents

Workflows and agent hints are not the same thing.

  • workflows.default is the canonical repo operational path
  • agent.default_task is an agent-facing hint
  • agent.entrypoint is an agent bootstrap hint

Use this split when needed:

  • human default workflow = broader, more complete, more representative
  • agent default task = narrower, cheaper, safer

Do not force the repo default workflow to become agent-shaped unless that really is the main operator path.

Good workflow names:

  • app
  • backend
  • frontend
  • runtime
  • worker
  • ai

Good intents:

  • local_development
  • backend_development
  • frontend_development
  • local_runtime

Prefer names that describe the operational path, not the implementation detail.

Readiness reuse

Workflows can now reference reusable readiness.probes directly. They can also reference reusable attached surfaces directly.

Use that when:

  • one readiness probe should be shared by more than one workflow
  • the same readiness target should also appear as a named check
  • you want doctor to validate workflow readiness without forcing an inline shell command such as node -e "fetch(...)" into the contract

Use literal url probes when the endpoint is external or not modeled by Ota topology yet.

Use target-based probes when the endpoint already belongs to:

  • one declared task listener
  • one declared service endpoint
  • one canonical local topology path whose host, port, and endpoint identity should not drift from the rest of the contract

Reusable HTTP probes use the same canonical request surface as runtime/service readiness:

  • method
  • headers
  • success.status
  • body.contains

Success-rule authoring is flexible:

  • omit both expect_status and success.status for the normal default 200
  • use expect_status when one shorthand status is clearer
  • use success.status when you want multiple accepted statuses

Use checks[].probe when the same underlying probe should also participate in the explicit ota check surface with a named severity.

Use readiness.surfaces when the workflow should prove one runtime surface already owned by the repo topology instead of restating a probe target or literal URL.

Use readiness.signal when a packaged/quickstart path should report extra startup signals without blocking readiness verdicts:

  • readiness.signal.checks
  • readiness.signal.probes
  • readiness.signal.surfaces

Signal findings are surfaced as informational diagnostics in ota doctor / ota up output and JSON, but they do not flip repo readiness to not_ready.

Each readiness item must appear in one lane only: gating (readiness.*) or signal (readiness.signal.*), not both.

Design rule

Use workflows when they make the repo's operational truth more explicit.

Do not use them to create a second task system.

The best workflow is the one that makes ota doctor, ota up, and ota execution plan answer the same question clearly:

what is the canonical way to make this repo useful?