Task Files
Start with a single-task workflow using top-level steps, then split a growing workflow into auto-discovered task files under tasks/.
A workflow usually starts as one task and grows into many. OrchStep gives you two features to keep that growth tidy:
- Top-level steps - when you only have one task, skip the
tasks:wrapper entirely. - The
tasks/directory - when you have several, give each its own file and OrchStep discovers them automatically.
Neither needs configuration. This page walks through both with a realistic deploy pipeline, then shows how they compose.
Part 1: the simplest workflow (top-level steps)
When a workflow does exactly one thing, the tasks: main: boilerplate is pure
overhead. Write the steps at the top level instead:
# orchstep.yml
name: deploy-web
desc: Deploy the web app to staging
steps:
- name: build
func: shell
do: echo "Building web app"
- name: upload
func: shell
do: echo "Uploading to staging bucket"
- name: done
func: shell
do: echo "Deployed to staging"Run it with no task name - top-level steps become an implicit task called main,
which is the default:
orchstep runWorkflow: deploy-web
Task: main
Step: build
$ echo "Building web app"
Building web app
[ok] build
Step: upload
$ echo "Uploading to staging bucket"
Uploading to staging bucket
[ok] upload
Step: done
$ echo "Deployed to staging"
Deployed to staging
[ok] done
Result: successWhy use it
- Less ceremony. No
tasks:, no naming a task you only ever call one way. - Same engine. You still get steps, outputs, branching, loops,
defaults:,modules:- everything works, it is just a shorthand for a singlemaintask. - A clean upgrade path. When you need a second task, you wrap your steps under
tasks: main:and keep going (Part 2).
Tip: top-level
steps:and atasks:section are mutually exclusive. If you write both, OrchStep stops with a clear message telling you to move your steps undertasks: main:. Pick one shape per file.
Part 2: splitting tasks across files (the tasks/ directory)
As a workflow gains stages - build, test, deploy, migrations - a single file gets
long and merge-conflict-prone. Move each task into its own file under tasks/.
OrchStep scans that directory and turns every file into a callable task. No
imports, no registration.
Here is a my-service pipeline split across files. Click through the tree:
name: my-service
desc: Build, test, and deploy my-service
# Top-level steps (Part 1) orchestrate the discovered tasks below.
steps:
- name: build_stage
task: build
- name: test_stage
task: test
- name: deploy_stage
task: deployA discovered task file is just a task body: a desc: (optional) and a steps:
array. The task name comes from the file's path, not from inside the file.
Listing what was discovered
orchstep list-tasks shows every callable task, including the discovered ones and
the implicit main:
orchstep list-tasksbuild
db-migrate
deploy
main (default)
testNote db-migrate - the nested tasks/db/migrate.yml flattened its path into a
hyphenated name. And main (default) is the top-level-steps task from
orchstep.yml.
Running them
Run one discovered task directly by its name:
orchstep run buildDiscovered 4 task file(s) from tasks/ directory
Workflow: my-service
Task: build
Step: compile
$ echo "go build -o bin/my-service ./cmd/..."
go build -o bin/my-service ./cmd/...
[ok] compile
Result: successOr run the whole pipeline (orchstep run -> main), which delegates to each
discovered task in turn:
orchstep runDiscovered 4 task file(s) from tasks/ directory
Workflow: my-service
Task: main
Step: build_stage
Task: build
Step: compile
...
[ok] build_stage
Step: test_stage
Task: test
Step: unit
...
[ok] test_stage
Step: deploy_stage
Task: deploy
Step: smoke
...
[ok] deploy_stage
Result: successTip: with a directory full of tasks,
orchstep menuopens a keyboard-driven picker over all of them - single-keystroke hotkeys for public tasks, fuzzy search by name or description.
The rules (worth knowing up front)
NAMING = PATH
tasks/build.yml becomes build. Nested tasks/db/migrate.yml becomes
db-migrate - directory separators are flattened to hyphens.
MAX DEPTH 2
You can nest one folder deep (tasks/db/migrate.yml). Going deeper is rejected
so names stay predictable.
UNDERSCORE = HIDDEN
Files and folders starting with _ are skipped - perfect for _helpers.yml
or a _drafts/ folder of work in progress.
INLINE WINS
A task defined inline in orchstep.yml takes precedence over a discovered
file of the same name, so you can override a shared task locally.
Two more things the engine enforces, so you get a clear error instead of a silent surprise:
- A file is only a task if it has a
steps:field. A straynotes.ymlwith nosteps:is quietly ignored, not loaded as a broken task. - Name conflicts fail loudly. If two files resolve to the same task name, OrchStep stops and tells you which two files collided so you can rename one.
Tip (the colon gotcha): a
do:value with a bare colon-space - likedo: echo "tests: 42 passed"- is read by YAML as a nested mapping and the file is skipped as invalid. Quote the whole value (do: 'echo "tests: 42 passed"') or avoid the colon. This bites everyone once.
How the two compose
You already saw it: the my-service root used top-level steps (Part 1) whose
only job is to call the discovered tasks (Part 2) in order. That is the sweet
spot for most projects:
- A short, readable
orchstep.ymlthat reads like a table of contents. - Each real unit of work in its own file, owned and reviewed independently.
- Every file individually runnable (
orchstep run test) for fast inner loops.
You are not limited to this shape - the root can be a full tasks: section with a
main orchestrator and helpers too. Use top-level steps when the root is purely an
orchestrator; use tasks: when the root file itself owns several tasks.
When to reach for which
| You have... | Use |
|---|---|
| One task, one file | Top-level steps: - skip the tasks: wrapper |
| A few tasks, one file | A tasks: section with main + helpers (see Composing Tasks) |
| Many tasks, growing | tasks/ directory - one file per task, auto-discovered |
| Shared tasks across projects | Promote them to a module |
Where to go next
- Composing Tasks - how
task:calls pass parameters and return outputs. - Project Layout - full directory layouts for workflows, envs, and modules.
- Using Modules - share discovered tasks across repositories.
- Runnable example:
serve-task-calls- a pipeline that calls auto-discovered task files (incl. nestedtasks/deploy/); on theserveLIVE graph the run descends into each called task.