Loop YAML Schema
Full reference for loop configuration files (.dmx/loops/{name}.yaml).
Top-level fields
| Field | Type | Required | Default |
|---|---|---|---|
name | string | Yes | — |
skills | list[string] | Yes | — |
trigger | object | No | {type: manual} |
goal_state | string | No | "" |
repeat_until | string | null | No | null |
validators | list[object] | No | [] |
on_optional_failure | warn | ignore | No | warn |
failure_handling | pause | fail | No | pause |
human_gate | boolean | No | true |
on_complete | object | No | (see below) |
require_branch | base | null | No | null |
name
name: devThe loop name. Must be non-empty and match the filename stem exactly — a file named dev.yaml must have name: dev, or loading fails.
skills
skills:
- implement-next-phase
- commitOrdered list of skill names to execute. Must contain at least one skill. Skill names correspond to the name field in skill .md files. See Command Reference.
Each skill is resolved from .dmx/skills/ in the repo, then any shared sources, then the bundled skills. In each location it can be a flat {name}.md file or a {name}/SKILL.md folder.
Skills execute in order. When human_gate: true, the loop pauses after each skill.
trigger
trigger:
type: manual # or: on_completemanual: the loop must be started explicitly with /dmx/run-loop name.
on_complete: the loop fires automatically when another loop's on_complete.on_success.trigger_loop references it.
goal_state
goal_state: "All phases implemented and committed, no unchecked tasks remaining"Plain-English description of what the loop is trying to achieve. Passed to validators in the loop context (goal_state in the input contract). Not persisted to the task state file — it's read fresh from the loop config on each run.
repeat_until
repeat_until: all_phases_completeIf set, the skill sequence repeats until the condition evaluates to true. After each iteration, the condition is checked before validators run.
all_phases_complete: checks .dmx/tasks.md for unchecked - [ ] items. Condition is met when all items are - [x].
Omit (or set to null) for loops that should run once.
validators
validators:
- tool: run_tests
timeout_seconds: 630
checks:
- name: tests_pass
required: true
- tool: spec_adherence
checks:
- name: scope_matches_spec
required: trueList of validator tools to run after all skills complete. Each validator:
tool: validator script name. Resolved to validators/{tool}.py in the repo root, then to validators/{tool}.py in each declared shared source; falls back to the bundled validator with that name.
checks: named checks to verify in this validator's output. Each check:
name: must match a check name in the validator's JSON outputrequired:true(must pass) orfalse(warning if fails)
timeout_seconds: optional. See below.
timeout_seconds
validators:
- tool: run_tests
timeout_seconds: 1200How long this validator may run, in seconds. Must be a whole number of at least 60. When omitted, the runner allows 630 seconds.
The limit is passed to the validator as loop_context.timeout_seconds. The bundled run_tests stops the test command 30 seconds before it, so a slow suite is reported as a test timeout. That's why the minimum is 60: there's always time left to stop the test command cleanly.
To give a validator more time, set timeout_seconds in the loop's YAML: your .dmx/loops/{name}.yaml if you have one, otherwise the shared source that provides the loop. To change a bundled loop, copy it to .dmx/loops/{name}.yaml first.
on_optional_failure
on_optional_failure: warn # or: ignoreWhat happens when an optional check (required: false) fails:
warn: warning is emitted, loop advancesignore: failure is silently ignored
failure_handling
failure_handling: pause # or: failWhat happens when a required check (required: true) fails:
pause: loop pauses. You fix the issue and run/dmx/loop-continueto retry.fail: loop terminates. Job is marked failed.
human_gate
human_gate: true # or: falseWhen true, the loop pauses after each skill completes and waits for /dmx/loop-continue before proceeding to the next skill (or to validators, if it was the last skill).
When false, skills run back-to-back automatically.
on_complete
on_complete:
on_success:
trigger_loop: validate # or: null
on_failure:
trigger_loop: null
on_warning:
trigger_loop: nullThree sub-keys, each with a trigger_loop value:
on_success: all required validators passon_failure: one or more required validators failon_warning: required validators pass but optional validators fail
trigger_loop is either a loop name (starts that loop immediately) or null (loop chain ends).
Default values if on_complete is omitted:
on_complete:
on_success:
trigger_loop: null
on_failure:
trigger_loop: null
on_warning:
trigger_loop: nullrequire_branch
require_branch: baseRestricts which branch the loop can be started from. The only supported value is base, meaning the loop must be started from branch_base (the integration branch configured in .dmx/config.md). Omit for loops that operate on an already-identified ticket — this is only meaningful for loops that establish a new ticket/branch identity, like the bundled Spec Loop.
When set, run_loop also skips resolving a job id from any existing spec.md/branch and starts the run under a temporary id instead, promoted to the real job id once the ticket is created.
The check also runs when another loop chains into this one through on_complete. If it blocks the chain, the finished loop reports its own outcome plainly, along with the reason the next loop couldn't start.
In a repository with no commits yet, a require_branch: base loop can't start: it would branch from branch_base on GitHub, which needs at least one commit. Commit and push first.
Complete example
name: dev
skills:
- implement-next-phase
- commit
trigger:
type: on_complete
goal_state: "All phases implemented and committed, no unchecked tasks remaining"
repeat_until: all_phases_complete
validators:
- tool: run_tests
timeout_seconds: 630
checks:
- name: tests_pass
required: true
on_optional_failure: warn
failure_handling: pause
human_gate: true
on_complete:
on_success:
trigger_loop: validate
on_failure:
trigger_loop: null
on_warning:
trigger_loop: null