dmx
Core Concepts

Jobs, Tasks & Persistent State

When a loop runs, dmx creates a persistent record of what happened. That record is what makes background execution safe and what produces the audit trail.

Jobs

A job is a unit of work corresponding to a ticket or branch. One job tracks all the loop executions for a single piece of work, from the first Spec Loop through the final Release Loop.

Each job has a unique ID, and job state lives in .dmx/jobs/{job_id}/. The job ID is the ticket in .dmx/spec.md's frontmatter when the spec's branch matches the branch you're on and the ticket is a real ID. Otherwise it's the current branch name. So a repo with no ticketing gets one job folder per branch, and a spec left over from another ticket can't send your work to that ticket's folder.

Tasks

A task is a single loop execution within a job. Every time a loop runs, whether it completes, pauses, or fails, it creates a task record.

Each task tracks:

FieldDescription
job_id / task_idIdentifiers for the job and this specific execution
loop_nameWhich loop ran (e.g. spec, plan, dev)
statuspending, running, paused, iterating, validating, complete, failed
skills / current_skill_indexThe full ordered skill list and how far execution has progressed
skills_completedWhich skills have finished
skill_outputsOutput recorded from each completed skill, keyed by skill name
validator_resultsPass/fail results reported by each validator that ran
outcomesuccess, warning, or failure, once validators have run
iteration_countHow many times the skill sequence has repeated (for repeat_until loops)
timestamp / updated_atWhen the task started and was last updated

Task state is written to .dmx/jobs/{job_id}/{loop_name}-{task_id}.json.

Finding the active run

There's no separate pointer file tracking which loop is active. Instead, loop_continue (and loop_advance) finds it by scanning .dmx/jobs/{job_id}/ for the one task file whose status is still non-terminal — pending, running, paused, iterating, or validating. Everything else in that folder is a completed or failed record from earlier in the ticket's lifecycle.

One exception: the Release Loop's run. Before create-pr commits, it marks that run complete so the PR doesn't contain a job file stuck at running. The run's outcome stays empty until /dmx/loop-continue runs check_pr_ready. On a feature branch, a complete file with no outcome is still the active run. On your integration or production branch, it's history.

Only files that look like task state — with loop_name, task_id, and status fields — are considered. Skills can write their own artifacts to the same folder (the validate skill writes validation-report.json there), and those are skipped.

Because this lookup is scoped to the job id derived from your current branch, it's naturally isolated per branch. Pausing a loop on one branch, switching to another, and running a loop there doesn't touch the paused run — each job folder only ever has at most one non-terminal task at a time.

The Spec Loop is a special case: it creates a brand-new ticket, so it doesn't have a real job id yet when it starts. It runs under a temporary _pending-{task_id} folder until create-ticket writes a spec for the new branch, at which point the folder is renamed to the real job ID.

Validators run in the background

After a loop's last skill, its validators run on a background worker and the run's status is validating. A long test suite can't time out your IDE's connection to dmx or block other requests. The agent calls the loop_status tool, which waits up to 25 seconds for the result and returns the outcome as soon as it's ready.

If dmx restarts while validators are running, loop_status says validation was interrupted, and /dmx/loop-continue runs the validators again.

Inspecting loop state

Job and task state is plain JSON committed to your repo. To see the current job, its active task, which skills have run, and validator results, ask your AI assistant about the current loop state. It calls loop_status, which reports the active loop without changing it. You can also look for the non-terminal task file directly under .dmx/jobs/{job_id}/.

Why persistent state matters

Most AI coding sessions are ephemeral. Close the window, lose the context.

dmx's persistent state changes this in two ways.

Resumability. If you close your IDE mid-loop, the state is preserved. Open it the next day, run /dmx/loop-continue, and the loop picks up exactly where it left off.

Auditability. Every loop run and validator result is recorded. If someone asks how a change was verified, the answer is in the job directory: which validators ran and what they found.

State in your repository

All dmx state lives in .dmx/, committed to your repository. This means every developer can see the current job state, reviewers can inspect what the AI produced and how validators ran, and there is no external dependency to manage.

When a loop run finishes (it completes or fails, rather than pausing or iterating), the runtime commits its final task file and the session-note breadcrumb it adds to activeContext.md, with a message like chore: sync loop state for release (job DM-123). This matters most for the Release Loop: nothing runs after it to commit its state, and close-ticket deletes the branch. The commit is skipped silently outside a git repository or when there's nothing to commit. If it's attempted and fails (for example, a pre-commit hook rejects it), the loop's response includes a warning asking you to check git status .dmx/ and commit by hand.

The runtime doesn't commit a paused or iterating run, since its state will change again on the next /dmx/loop-continue. It also never commits while your integration or production branch is checked out. It pushes that commit only when it's the only unpushed commit on a branch with an open PR; otherwise it warns you and leaves the push to you.

The tradeoff is that .dmx/jobs/ grows over time. Archiving or pruning closed job directories periodically is reasonable, just as you would clean up merged branches.

On this page