theway
Concepts

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>.yaml and a prompt prompts/<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.yaml or <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.md or standards/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 }}
  • version is theway.workflow/v1beta.
  • default: true marks the workflow theway workflow takes 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 shipped develop sets it.
  • inputs declares what the workflow takes, each with a description, required (default false), a default (a string, refused together with required: true) and a type: text (the default) or change, whose value must be a valid change ID.
  • runs-on is where every step runs unless the step sets its own.
  • Any other key is an error.

Steps

Each step is a map:

Key
usesrequired: the prompt it runs, prompts/<name>.md
withthe prompt's inputs: a string, a number or a boolean each
runs-onagents or session, over the workflow's
modelopus, sonnet or haiku, over the prompt's
matrixone key and a list of values: one run per value
on-failthe prompt that fixes a fail, then the step runs again
trieshow 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 with sets them; one it leaves out takes its default, else the empty string. A required input must be set to something other than "".
  • model is 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.md or prompts/spec-template.md.

Inputs and references

Two references are replaced, in a with value and in a prompt's body:

  • ${{ inputs.<name> }}: in a with value, the workflow's input; in a body, the prompt's input.
  • ${{ matrix.<key> }}: in a with value of a step with a matrix, the run's value. An on-fail takes 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 its on-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: 2

When 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 workflow theway: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

WorkflowInputsSteps
develop (default)changeimplement in your session, commit: "no": the tasks on the working tree, nothing committed
agentschangebranch (session), implement, review through two lenses, triage with fix-review on fail, open-pr, ci with fix-ci on fail (2 tries)
engineeringrequestscaffold (session), spec-review through grounding and implementability, spec-triage with fix-spec on fail
engineering-lightrequestscaffold-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-on and steps present and valid, default a boolean, every input declaration valid, no unknown key.
  • Each step: uses names a prompt found in the project or the home that has a valid header and a body; every with key is an input the prompt declares, with a string, number or boolean value; every required input is set; matrix has one key and a non-empty list of strings; on-fail names a valid prompt and comes with tries from 1 to 10; runs-on and model are valid; a step through agents has a model, from the step or its prompt, and so has its on-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 valid model, valid input declarations, no unknown key, and no body reference to an input it does not declare.
  • --input values: each one a declared input, a valid change ID for a type: change input, 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:

v1v1beta
A step file, steps/<name>.yaml (theway.step/v1), a tree of agentsOne 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 lensesmatrix
A loop with until and maxon-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 fieldsThe report, which every later step reads
when: … block:The prompt answers blocked
A gate entryA step in your session that asks you, and answers blocked on a stop
agents: true or falseruns-on: agents or session
input: changes or input: requestinputs, with type: change for a change ID
A step param, and workflow.defaults.<step>.<param> in config.yamlA with value in the workflow file, or the prompt's header (default, model)
pushes and accessWhat 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.

On this page