Plans and Steps#
Two markdown surfaces in a node’s data directory drive its work. Step
files under steps/ are the node’s per-iteration script: every iteration
executes them in order, and each file can tune how its own launch runs.
Plan files under plans/ are the records the agent writes while
planning: each is stamped with the run and iteration that produced it, so the
node’s decision history stays reconstructible after the fact.
Both directories live in the node directory (.fractal/<branch>/ inside
the node’s worktree). Step files are authored and edited by the operator
(and by a parent node when it steers); plan files are normally created by
the agent itself, via fractal plan init during the seeded 01-PLAN
step.
Plan files#
A plan file is a markdown file under <node_dir>/plans/ named
{timestamp}-{run.iter}-{name}.md:
timestampis the creation time in UTC (ISO 8601, millisecond precision), so two plans written in the same iteration get distinct names.run.iteris the iteration reference — run 2, iteration 5 is2.5. Iterations number from 1 within each run, so the reference pins the plan to one specific loop pass.nameis a short snake_case slug describing the plan.
The file is seeded with a single H1 line, # {run.iter} {title}, so the
originating iteration is human-readable inside the file as well as in the
filename; the agent writes the plan body below it. A plan for iteration
2.5 might be plans/2026-01-01T00:00:00.000Z-2.5-refactor_scheduler.md
beginning:
# 2.5 Refactor Scheduler
The seeded 01-PLAN step instructs the agent to create a plan file every
iteration (one per concern when the work splits cleanly), check it against
the remaining time and cost budget, and trim it to fit. Because plans are
plain files in the node directory, they are committed with the node’s work
product and survive on the node’s branch.
Creating a plan#
fractal plan init creates the file and prints its absolute path:
$ fractal plan init --name=refactor_scheduler --iter-ref=2.5
/home/user/myproject/.worktrees/main.parser/.fractal/main.parser/plans/2026-01-01T00:00:00.000Z-2.5-refactor_scheduler.md
--name <slug>(required)Short descriptive slug for the plan. Strictly validated: letters, digits, and underscores only (
[A-Za-z0-9_]+).--title <text>Title for the seeded H1. Defaults to the de-slugged name (
refactor_schedulerbecomesRefactor Scheduler).--iter-ref <run.iter>(required; reads theITER_REFenvironment variable)The
run.iterreference, strictly validated to the2.5form. The loop exportsITER_REFinto every agent launch, so inside a run the flag can be omitted — the seed steps callfractal plan init --name=<name>with no--iter-ref. Outside a run (an operator at a shell) the flag must be passed explicitly.--path <dir>(default.)Worktree directory identifying the node.
Creation is exclusive: if a file with the same name already exists, the command errors rather than silently replacing the earlier plan.
Listing plans#
fractal plan list prints an iteration’s plan paths, one per line, sorted
by filename (chronological, since the timestamp leads):
$ fractal plan list --iter-ref=2.5
/home/user/myproject/.worktrees/main.parser/.fractal/main.parser/plans/2026-01-01T00:00:00.000Z-2.5-refactor_scheduler.md
--iter-ref <run.iter>(required; readsITER_REF)The
run.iterreference to list plans for.--path <dir>(default.)Worktree directory identifying the node.
Plans are matched by globbing the {run.iter} segment of the filename, so
the listing returns every plan the iteration wrote — zero, one, or many —
without relying on modification times. When there are none, the notice
No plans for this iteration. goes to stderr; stdout stays a pure
path-per-line surface, safe to pipe.
The exact CLI surface is on the fractal plan page.
Step files#
Step files live at <node_dir>/steps/NN-<NAME>.md. Each iteration of the
loop executes them in lexicographic filename order; the NN- digit prefix
is what orders them, and the step’s name is the filename stem past its
first dash (02-EXECUTE.md is step EXECUTE). A fresh node is seeded
with the following steps:
00-PREPARE— merge the parent branch, survey children.01-PLAN— orient in memory and the project wiki, write the iteration’s plan file, check it against the budget.02-EXECUTE— execute the plan, including spawning and managing child nodes.03-REVIEW— review the diff, fix issues, update memory.04-COMMIT— signal completion when the requirements are met, thenfractal commit.
The seed is a starting point, not a fixture: add, remove, rename, or reorder
files freely. Steps are re-discovered and re-parsed at the top of every
iteration, so edits land at the next iteration boundary without a restart —
step files are a live steering surface for a running node. Discovery is
strict, and a violation fails the iteration loudly: an empty steps/
directory (no step files), a file without the NN- digit prefix, or
files mixing digit-prefix widths (invalid step files). A step file that
is not valid UTF-8 aborts the run at preflight, naming the file.
Step structure#
A step file is an optional frontmatter block followed by a markdown body:
---
requires_approval: true
timeout: 10m
---
## Execute
Execute the plan. Work only under $SCOPE_DIR ...
The frontmatter is not YAML. It is a strict flat-scalar grammar: the
block opens with --- on the very first line and closes at the next
---; each line inside must be a lowercase key (letters and underscores),
a colon, and a non-empty value. Nothing nests, nothing is quoted specially,
non-matching lines are ignored, and the first occurrence of a key wins. A
file whose first line is not --- simply has no frontmatter.
The body is the step’s instruction text. At launch the loop strips the
frontmatter and assembles the prompt as the node’s task contract
(NODE.md), the step body, and any active mode documents, rendering
$VAR and ${VAR} placeholders from the template-variable map
(unknown placeholders pass through verbatim). Prompt assembly and the full
variable vocabulary are covered in The Iteration Loop.
Step config vs node config#
A node’s run parameters live in its config.json (see
Configuration). A step’s frontmatter can override a small, fixed
subset of them for that step alone. The complete set of keys a step may set:
Key |
Values |
Effect |
|---|---|---|
|
|
Gates the step: after it succeeds, the loop waits for the parent’s
|
|
an agent command |
Overrides the node’s |
|
a route name (e.g. |
Overrides the node’s effective |
|
a model id |
Overrides the node’s |
|
a provider-specific level |
Overrides the node’s |
|
a duration ( |
Substitutes for the node’s |
|
|
|
Any other frontmatter key is parsed but has no effect. Everything else in the node’s configuration — budgets, retries, pacing, iteration caps — is node-level only and cannot be set per step.
Precedence when both define a value:
agent,provider,model,effort— step frontmatter wins over node config, for that step only. When the node config is itself unset,agentandproviderresolve up the ancestor chain (the value the node inherited at init), andmodelfalls back to the agent’s own config-file default — which is recorded on the step row but not passed on the command line. See Agent Backends for the agent, provider, model, and effort surfaces.timeout— the frontmatter value replaces the nodestep_timeoutentirely; it does not tighten it, so it may be looser. The run and iteration walls (timeout,iter_timeout) still bound the launch: the effective limit is the minimum of the run remaining, the iteration remaining, and the step’s own ceiling.detached— nodedetached: truemakes every step a fresh invocation and is pinned at boot; a step-leveldetached: truedetaches a single step of an otherwise continuous node. The two do not combine: a non-empty step value in a detached node is a per-step configuration error.
How the loop consumes steps#
At the top of every iteration the loop re-discovers and re-parses the step files, then runs each in order. Around each step:
Signal checkpoints. The loop checks for a pause before every step (parking with no commit — the dirty worktree is the state resume continues from) and for a stop or a tripped subtree budget ceiling between steps.
SYNC. When the node’s
syncconfig is enabled (the default), a SYNC pseudo-step precedes each step: a separate agent turn, with its own database row, that processes radio traffic and steering before the step’s own launch. A failed SYNC is non-fatal — the loop continues to the step — but a timed-out SYNC fails the iteration.Finish drain. Under a finish signal, the loop waits for active children to drain before the final step, so the last step runs with the subtree settled.
Launch. The prompt is assembled and the agent is launched, bounded by the effective time limit and the per-step budget leash (see The Iteration Loop). In a continuous node, the iteration’s steps resume one agent session per agent, so context carries from step to step; a detached node (or a
detached: truestep) gets a fresh invocation instead.Recording. Every launch attempt books its own step row in the central database — status, exit code, session, cost, and a short reason on abnormal ends — so
fractal node activityreads honestly per attempt.Retries. Only a failed launch retries (
step_retries, default 1, after astep_retry_backoff, default10s). Timed-out, paused, and budget-skipped steps are deliberate outcomes and never retry.Approval. A step marked
requires_approval: truethat succeeds holds the iteration until the parent runsfractal node approve(the parent’sfractal node pendinglists waiting steps). The wait counts against the run and iteration deadlines — a gated step can time out waiting — and reserve mode (budget wind-down) skips the gate. Each retry attempt re-arms its own gate.
A step that ultimately fails ends the iteration: the never-run tail steps
are recorded as stopped rows carrying failed on <step> so the
activity log answers which steps never ran, and any work product on disk is
force-committed as a backstop. Lifecycle outcomes, timeouts, and budgets are
covered in The Iteration Loop and Node Lifecycle.