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:
tasksare execution primitiveschecksare named readiness assertionsreadiness.probesare reusable transport-level readiness definitionssurfacesare reusable runtime endpoint definitionsservicesare dependenciesworkflowsare 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
setupandrunare not the same thing- humans keep asking "what should I run first?"
ota upshould 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 doctorandota upoptimize 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 doctordiagnoses the default workflow by defaultota checkchecks the default workflow by defaultota upprepares the default workflow by defaultota execution planresolves 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:
prepareis explicit host bootstrap before setupsetupis repo preparationrunis 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.setupinstalls dependenciestasks.devstarts the appchecks.app-healthprobes the appsurfaces.backenddefines the reusable app endpointworkflows.appsays 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.defaultis the canonical repo operational pathagent.default_taskis an agent-facing hintagent.entrypointis 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.
Recommended patterns
Good workflow names:
appbackendfrontendruntimeworkerai
Good intents:
local_developmentbackend_developmentfrontend_developmentlocal_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
doctorto validate workflow readiness without forcing an inline shell command such asnode -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:
methodheaderssuccess.statusbody.contains
Success-rule authoring is flexible:
- omit both
expect_statusandsuccess.statusfor the normal default200 - use
expect_statuswhen one shorthand status is clearer - use
success.statuswhen 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.checksreadiness.signal.probesreadiness.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?