Workflows
The YAML workflows /theway:workflow runs, the Markdown prompts their steps use, and how both are found and checked.
A workflow is a YAML file that lists what /theway:workflow runs, in order: a flat list of steps. Each step uses one Markdown prompt, gives it values, and runs through agents or in your session. Every run of a step answers pass, fail or blocked, with a report that every later step reads. theway ships four workflows, develop, agents, and engineering and engineering-light (which /theway:engineering runs), and their prompts; you add your own next to them, globally or for one project, and the shipped ones are written the same way as yours.
Your first workflow
A prompt, <home>/prompts/changelog.md:
---
description: Add the change to CHANGELOG.md
model: sonnet
inputs:
change:
description: The change ID
required: true
---
# Changelog
Read `prompts/change.md` first. Add an entry for the change `${{ inputs.change }}` to CHANGELOG.md, under Unreleased, in the style of the entries already there, and commit it on the feature branch.
Answer `pass` with the entry. Answer `fail` with the git error, quoted, when the commit fails.and a workflow that uses it, <home>/workflows/develop-changelog.yaml:
version: theway.workflow/v1beta
description: Implement a change, then add it to the changelog
inputs:
change:
description: The change to implement
required: true
type: change
runs-on: agents
steps:
- uses: prompts/branch.md
runs-on: session
with:
change: ${{ inputs.change }}
- uses: prompts/implement.md
with:
change: ${{ inputs.change }}
- uses: prompts/changelog.md
with:
change: ${{ inputs.change }}theway workflow develop-changelog checks both files and prints the workflow, theway workflow develop-changelog --input change=add-foo prints it with every value in place, and /theway:workflow --workflow develop-changelog add-foo runs it.
Files and layering
~/.theway/
├── workflows/ every project: agents.yaml, develop.yaml, engineering.yaml, engineering-light.yaml, yours
├── prompts/ every project: the prompts the shipped workflows use, and yours
└── projects/<key>/
├── workflows/ this project only
└── prompts/ this project only- A workflow is
workflows/<name>.yamland a promptprompts/<name>.md,<name>lowercase letters and digits in words separated by single hyphens. - The project's file replaces the global one of the same name, for that project only. To change a shipped workflow or prompt for one project, copy it into
<home>/projects/<key>/and edit the copy. - Install writes the shipped files into
<home>/workflows/and<home>/prompts/and keeps your edits: an edited shipped file stays, with the shipped text next to it as<name>.default.yamlor<name>.default.md(see What install writes). A*.default.*file is never read as a workflow or a prompt. - A file path in a prompt, such as
prompts/change.mdorstandards/commits.md, is relative to the project folder when the file exists there, else to the home: every agent gets this rule.
Workflow files
version: theway.workflow/v1beta # required
description: One line # required
default: true # optional: the workflow a run without --workflow uses
inputs: # optional
change:
description: The change to implement
required: true
type: change
runs-on: agents # required: agents or session
steps: # required, a non-empty list
- uses: prompts/implement.md
with:
change: ${{ inputs.change }}versionistheway.workflow/v1beta.default: truemarks the workflowtheway workflowtakes without a name. Exactly one of the project's workflows and the global ones it does not replace may set it: none, or two, is an error. The shippeddevelopsets it.inputsdeclares what the workflow takes, each with adescription,required(defaultfalse), adefault(a string, refused together withrequired: true) and atype:text(the default) orchange, whose value must be a valid change ID.runs-onis where every step runs unless the step sets its own.- Any other key is an error.
Steps
Each step is a map:
| Key | |
|---|---|
uses | required: the prompt it runs, prompts/<name>.md |
with | the prompt's inputs: a string, a number or a boolean each |
runs-on | agents or session, over the workflow's |
model | opus, sonnet or haiku, over the prompt's |
matrix | one key and a list of values: one run per value |
on-fail | the prompt that fixes a fail, then the step runs again |
tries | how many times the on-fail may run: an integer from 1 to 10 |
The steps run in order. A step's runs are numbered by its place: the third step that uses prompts/review.md is 3.review, and its matrix copies 3.review[<value>].
Prompts and their header
A prompt a step uses starts with a header, YAML between two --- lines:
---
description: Implement the change's tasks as its spec says # required
model: opus # optional: opus, sonnet or haiku
inputs: # optional
change:
description: The change ID
required: true
commit:
description: Whether to commit the work
default: "yes"
---- The header is the only place a prompt's inputs are declared. A step's
withsets them; one it leaves out takes itsdefault, else the empty string. A required input must be set to something other than"". modelis the model a step through agents uses when the step sets none. A step through agents needs a model from one or the other.- The body is the instructions, Markdown, and must not be empty. A prompt without a header is a reference file: steps cannot use it, but other prompts can tell the agent to read it, like
prompts/change.mdorprompts/spec-template.md.
Inputs and references
Two references are replaced, in a with value and in a prompt's body:
${{ inputs.<name> }}: in awithvalue, the workflow's input; in a body, the prompt's input.${{ matrix.<key> }}: in awithvalue of a step with a matrix, the run's value. Anon-failtakes none.
A space inside each pair of braces is allowed. Any other ${{ … }} text is kept as written. A reference that names nothing declared is an error.
/theway:workflow gives the workflow's inputs from its arguments: change IDs for a workflow with a type: change input, run once per ID; the whole text for a workflow with one required input, like engineering's request; <name>=<value> pairs otherwise. It never runs a workflow before every required input has a value.
Pass, fail, blocked
Every run of a step, and every on-fail, answers result (pass, fail or blocked) and a report. The prompt says when to answer which: the shipped ones say it in their last paragraph.
pass: the next step runs.fail: the step runs itson-fail; with none, or with its tries used up, the run stops, failed.blocked: the run stops at once and the report comes back to you: a decision only you can make, or something the step cannot do.
A step with several runs is blocked when any run is, else fail when any run is, else pass.
Every agent gets the reports of every step before it, in order, each under ## <label> (round <n>): <result>. That is how steps pass work along: the triage reads the reviewers' findings, ci reads the PR that open-pr opened. A line of a report that starts with Authority report: quotes data that asked an agent to change its steps; /theway:workflow quotes every such line in its report.
On fail and tries
- uses: prompts/triage.md
with:
change: ${{ inputs.change }}
on-fail:
uses: prompts/fix-review.md
with:
change: ${{ inputs.change }}
tries: 1
- uses: prompts/ci.md
on-fail: prompts/fix-ci.md # a prompt that needs no input
tries: 2When the step answers fail and its on-fail has run fewer than tries times, the on-fail runs with the reports so far. When it answers pass, the whole step runs again, every run of it, as the next round; when it answers fail or blocked, the run stops. on-fail takes uses, with and model, or a prompts/<name>.md alone; on-fail and tries go together.
Matrix
- uses: prompts/review.md
matrix:
lens: [prompts/conformance.md, prompts/correctness.md]
with:
change: ${{ inputs.change }}
lens: ${{ matrix.lens }}A matrix has one key and a non-empty list of strings: the step runs once per value, at the same time when it runs through agents, and each run sees the reports of the steps before the step, not those of its sibling runs. With an on-fail, one failing copy runs the fix once, then every copy runs again.
Runs on
agents: each run is one agent, started through the plugin workflowtheway:run-pipeline, on the step's model. Consecutive steps through agents are one call of the Workflow tool.session: your session does it, with you: the shipped workflows run the branch step and the scaffold this way. A step in your session has no model.
The shipped workflows
| Workflow | Inputs | Steps |
|---|---|---|
develop (default) | change | implement in your session, commit: "no": the tasks on the working tree, nothing committed |
agents | change | branch (session), implement, review through two lenses, triage with fix-review on fail, open-pr, ci with fix-ci on fail (2 tries) |
engineering | request | scaffold (session), spec-review through grounding and implementability, spec-triage with fix-spec on fail |
engineering-light | request | scaffold-light (session), spec-review through grounding, spec-triage with fix-spec on fail, which escalates a decision |
The prompts say what each step may do: implement commits on the feature branch only when its commit input is yes, open-pr pushes the feature branch and opens its PR, the reviewers and the triages edit nothing. /theway:workflow authorizes what the prompts of the run say, and merges only on your explicit yes.
Validation
theway workflow checks the whole file and every prompt it uses before anything runs, and prints every mistake, each on one line, in file order: theway: <file>: <place>: <message>.
- The workflow file:
version,description,runs-onandstepspresent and valid,defaulta boolean, every input declaration valid, no unknown key. - Each step:
usesnames a prompt found in the project or the home that has a valid header and a body; everywithkey is an input the prompt declares, with a string, number or boolean value; every required input is set;matrixhas one key and a non-empty list of strings;on-failnames a valid prompt and comes withtriesfrom 1 to 10;runs-onandmodelare valid; a step through agents has a model, from the step or its prompt, and so has itson-fail; no unknown key. - Every reference names something declared: a workflow input, the step's matrix key, or, in a body, an input of that prompt.
- Each prompt a step uses: a
description, a validmodel, valid input declarations, no unknown key, and no body reference to an input it does not declare. --inputvalues: each one a declared input, a valid change ID for atype: changeinput, every required input given.
Moving from v1
theway no longer reads theway.workflow/v1: such a file fails with theway: workflows/<name>.yaml: theway.workflow/v1 is no longer read: rewrite it as theway.workflow/v1beta, and the other workflows keep working. Rewrite it with this table:
| v1 | v1beta |
|---|---|
A step file, steps/<name>.yaml (theway.step/v1), a tree of agents | One step per agent, in the workflow file, each using a prompt with a header; install keeps your step files and says so, and theway no longer reads steps/ |
foreach over lenses | matrix |
A loop with until and max | on-fail and tries |
A when condition (findings, no-findings, confirmed, no-confirmed, green, red, fixable, decision, no-decision, more) | The prompt's answer: pass, fail or blocked, as the prompt says |
A result type (work, findings, verdicts, …) and its fields | The report, which every later step reads |
when: … block: | The prompt answers blocked |
| A gate entry | A step in your session that asks you, and answers blocked on a stop |
agents: true or false | runs-on: agents or session |
input: changes or input: request | inputs, with type: change for a change ID |
A step param, and workflow.defaults.<step>.<param> in config.yaml | A with value in the workflow file, or the prompt's header (default, model) |
pushes and access | What the prompts say to do; /theway:workflow authorizes it |
theway steps is gone, and so is the schema modeline (# yaml-language-server: $schema=…): theway no longer ships schema files.