Loop Configuration
Loop configs are YAML files that define how a loop runs. They live in .dmx/loops/{name}.yaml in your repository.
Where configs live
dmx resolves loop configs in this order:
- App repo:
.dmx/loops/{name}.yamlin your repository - Shared sources:
.dmx/vendor/{source}/loops/{name}.yamlfor each source declared in.dmx/shared-sources.yaml, in declared order - Bundled defaults: the five default loops shipped with dmx
The repo always wins. A file at .dmx/loops/dev.yaml overrides the bundled Dev Loop entirely, and any shared-source version of it. Shared sources are optional; see Shared Sources to maintain loops once for many repositories.
To customize a loop, copy the bundled default into .dmx/loops/ and modify it. To create a new loop, create a new file. The filename stem becomes the loop name.
Full YAML schema
# Required — must match the filename stem
name: dev
# Required — ordered list of skill names to execute
skills:
- implement-next-phase
- commit
# Trigger — how the loop starts
trigger:
type: manual # manual | on_complete
# Goal state — plain-English description of success
goal_state: "All phases implemented and committed, no unchecked tasks remaining"
# Repeat until — condition to evaluate before running validators
# Currently supported: all_phases_complete
# Omit if the loop should run once only
repeat_until: all_phases_complete
# Validators — run after all skills complete (and repeat_until is met)
validators:
- tool: run_tests # validator script name
timeout_seconds: 630 # optional; default 630, minimum 60
checks:
- name: tests_pass
required: true # required: true | false
- tool: security_scan # a custom validator in validators/
checks:
- name: no_high_findings
required: true
- name: no_medium_findings
required: false
# What to do when an optional check fails
on_optional_failure: warn # warn | ignore
# What to do when a required check fails
failure_handling: pause # pause | fail
# Whether to pause at each skill boundary for human review
human_gate: true
# What to do when the loop completes
on_complete:
on_success:
trigger_loop: validate # loop name to start, or null
on_failure:
trigger_loop: null
on_warning:
trigger_loop: null
# Restrict which branch this loop can start from. Only meaningful for loops
# that establish a new ticket/branch identity. Omit otherwise.
require_branch: baseField reference
name
Required. The loop name. Must match the filename stem. Used in trigger_loop references and in /dmx/run-loop name.
skills
Required. Ordered list of skill names to execute. Skills run in order. The loop pauses between skills when human_gate: true.
Skill names correspond to the name field in skill .md files. Available skills are listed in Command Reference.
The loop runtime looks each skill up in this order:
- App repo:
.dmx/skills/in your repository - Shared sources:
.dmx/vendor/{source}/skills/for each declared source, in order - Bundled: the skills shipped with dmx
In each location, a skill can be a flat {name}.md file or a {name}/SKILL.md folder (the agentskills.io / Claude Code convention). If both exist, the flat file wins. A file at .dmx/skills/commit.md replaces the bundled commit skill whenever a loop runs it. Skill names must be plain slugs: letters, digits, _, and -.
trigger
How the loop starts.
| Type | When to use |
|---|---|
manual | The developer invokes /dmx/run-loop explicitly |
on_complete | The loop fires when the previous loop's on_success.trigger_loop names this loop |
goal_state
Plain-English description of what the loop is trying to achieve. Passed to validators as context (goal_state in the input contract).
repeat_until
If set, the skill sequence repeats until the condition is met. Currently supported:
all_phases_complete: repeats until all- [ ]items intasks.mdare checked off
Omit for loops that should run once.
validators
List of validators to run after all skills complete (and after repeat_until is satisfied). Each validator:
tool: the validator script to run (looked up invalidators/{tool}.py: repo first, then shared sources, then bundled)checks: named checks, each withrequired: true | falsetimeout_seconds: optional; how long the validator may run. Defaults to 630 seconds, minimum 60. Seetimeout_seconds.
Multiple validators run in order. All required checks across all validators must pass.
on_optional_failure
What happens when an optional check fails:
warn: warning is emitted, loop advancesignore: failure is silently ignored
Default: warn.
failure_handling
What happens when a required check fails:
pause: loop pauses, you fix and continuefail: loop terminates
Default: pause.
human_gate
When true, the loop pauses after each skill completes and waits for /dmx/loop-continue before proceeding. When false, skills run back-to-back automatically.
Default: true.
on_complete
What to do when the loop finishes:
on_success.trigger_loop: loop name to start automatically when all validators passon_failure.trigger_loop: loop name to start when required validators fail (uncommon)on_warning.trigger_loop: loop name to start when optional validators fail (uncommon)
Set to null to terminate without starting another loop.
require_branch
Restricts the loop to starting from branch_base (the integration branch set in .dmx/config.md). The only supported value is base.
Use this for loops that create a brand-new ticket/branch — like the bundled Spec Loop — where any pre-existing spec.md or current branch would be stale. Attempting to start such a loop from anywhere else is rejected with a message telling you to switch branches first. Leave it unset for loops that operate on an already-identified ticket.
The same check applies when another loop chains into this one through on_complete. If the chain is blocked (for example, a custom loop on a feature branch chains into spec), the finished loop reports its own outcome along with the reason the next loop couldn't start.
Omit for loops that should be startable from any branch.
Example: custom documentation loop
name: docs
skills:
- docs
trigger:
type: manual
goal_state: "Public API documented, docstrings complete, README updated"
validators:
- tool: lint_check
checks:
- name: no_lint_errors
required: false
on_optional_failure: warn
failure_handling: pause
human_gate: true
on_complete:
on_success:
trigger_loop: nullExample: hotfix loop (faster path)
name: hotfix
skills:
- implement-next-phase
- commit
- validate
- create-pr
trigger:
type: manual
goal_state: "Fix implemented, validated, PR opened against production branch"
validators:
- tool: run_tests
checks:
- name: tests_pass
required: true
on_optional_failure: warn
failure_handling: pause
human_gate: true
on_complete:
on_success:
trigger_loop: nullTips
Keep loop configs in version control. .dmx/loops/ should be committed to your repo. Loop configs are reviewed in PRs like any other code change.
Start from the bundled defaults. Copy the relevant YAML from The Default Loops and modify it. The defaults are designed to be good starting points, not constraints.
One loop, one responsibility. Avoid creating loops that try to do too many things. A loop that's too long is hard to review and hard to debug when validators fail.