skip to content

The pattern format

The normative reference for the pattern format: every primary word, table, key, reference form and closed value vocabulary the engine conforms to.

Normative reference for the pattern language: every primary word, table, key, reference form, and closed value vocabulary. The narrative guide is manual.md.

This document is the authority the engine conforms to: change it first and make the implementation conform. The bundled corpus and the interpreter carry one format, and a file whose header declares the retired version key is read only far enough to be refused with an incompatible-version error.

1. Contract

A pattern file is finite TOML data describing an orchestration. It performs no computation at load time and carries no expression language, embedded scripting language, arithmetic, variables, counters, or author-defined control operators. All effects are produced by three step kinds, all composition by four constructs, and all named behaviour is resolved at load into typed handles before execution.

The primary words are exactly these:

Word Kind Meaning
pattern definition A named sequence of steps and parameters.
agent producing step Runs one fresh untrusted agent session.
gate producing step Runs Gantry's authoritative project check package.
command producing step Runs a deterministic command and records its declared outputs.
for composition Sequentially runs one body pattern for every task in a directory list.
if composition Routes on one produced value, one step outcome, or absence.
stop composition Ends the run with an authored note.
call composition Invokes another pattern in the same composed definition namespace.
output value A declared file or directory path produced by exactly one step.

output is the only value word. It is never a step type and never appears as type = "output".

No other top-level or step type word is accepted. Type words of the retired format such as block, rule_block, counter, channel, inner_unit, script, goal-check, git, loop, fan-out, sub-pattern, include, terminate, conditional, validation-gate, write-map-spec, validate-map-spec, and classify-units are not aliases. A file using one of them as a primary table or type value is refused.

Unknown fields are load errors everywhere: in the header, patterns, parameters, steps, outputs, branch tables, list references, restore references, text tables, and step-specific properties. A reader must not silently ignore extension fields. Extending the language means extending this reference first, then the typed reader.

The reader accepts only files whose header declares the pattern-language compatibility marker, even when a retired-format construct could be mechanically translated. Compatibility for old run records, pins, ledgers, or archived display data is record compatibility: separate from pattern-language compatibility, and it must not expand the accepted grammar.

2. File Shape

A pattern file has one [header] table and one or more [patterns.<name>] definition tables:

toml
[header]
pattern_language_compatibility = "3"
entry = "build"
include = ["milestone.toml"]
on_stop = "write_handover"
description = "Build a plan through milestone briefs."

[patterns.build]

[[patterns.build.parameters]]
name = "plan_source"
kind = "path"
declared_plan_source = true

[[patterns.build.steps]]
name = "plan_milestones"
type = "agent"
prompt = "write-milestones"
outputs = [
  { name = "milestones", path = "milestones/" },
  { name = "plan_scope", path = "state/plan-scope.txt", values = ["work", "none"] },
]

Definitions compose into one flat namespace across the entry file and every file it includes. An included file's pattern definitions and its own include list compose into that namespace. Its run-level header keys are inert when included; the entry file's header alone configures the run. A file may be runnable by itself and includable by another file.

After include composition, the entry file's entry value must resolve to a pattern in the composed namespace.

Definition names are author-owned identifiers. The engine may report them and resolve references to them, but execution behaviour must not compare them to string literals. milestone, sprint, map, until, or any bundled name has no special meaning once the pattern is selected.

nothing is engine-provided and reserved. It names the identity pattern with no steps. No file may define a pattern, output, parameter, or step named nothing.

3. Header

The [header] keys are:

Key Required Meaning
pattern_language_compatibility yes Must be the string "3".
entry yes Pattern name where execution starts.
description yes Human-facing selection and documentation prose.
include no List of pattern files composed into the entry file's namespace.
merge no Pattern used for finish-time merge and merge-repair work.
on_stop no Hook pattern or hook table for engine stops.
default no Marks the one bundled or selected-tier pattern a flagless plan invocation uses.
cli_flags no Pattern-selection flags this entry pattern claims, each bound to entry parameters.

pattern_language_compatibility is the pattern file's reader contract. The run pin also stores a pattern-language compatibility marker for the frozen closure it is resuming. That marker protects reuse of an existing run under a different interpreter contract; it is not a second grammar.

include entries resolve by the same pattern-file tiers as ordinary pattern selection. They name whole files, never individual definitions inside them. The same included pattern file composes once, including through diamond includes or repeated same-name includes. Duplicate pattern names from different files across the composed closure are load errors, except for the reserved engine-provided nothing, which no file declares.

merge, when present, names a pattern in the composed namespace. The engine invokes that pattern for finish-time merge and merge-repair work. An omitted merge key selects the bundled default merge pattern. The selected merge pattern is not an on_stop hook and is not dispatched by stop class.

At run start, Gantry freezes the selected merge pattern and its include closure beside the entry pattern closure. merge-pattern.toml pins that selected merge pattern for resume. A run whose frozen closure predates merge-pattern.toml is backfilled once with the bundled merge closure, including troubleshoot, and then resumes from that frozen material.

default = true claims the flagless gantry <plan> selection. The registry must find exactly one loadable default claim when binding such an invocation. Omitted or false means the pattern is still selectable by name or path but is not the default.

[[header.cli_flags]] binds the fixed CLI selectors --map and --until to parameters of the entry pattern:

toml
[[header.cli_flags]]
flag = "map"
value_parameter = "source"

The allowed keys are:

Key Required Meaning
flag yes One of the engine-owned selector slots.
value_parameter yes Entry parameter receiving the flag's primary value.
passthrough_parameter no Entry parameter receiving selector-specific trailing text.

Each flag slot may be claimed at most once by one loadable registry entry.

Entry parameters may also be supplied on the run command line as --param <name> <value>, which takes exactly two following argv tokens: the declared entry parameter name and its literal value. Inline forms such as --param name=value, omitted names, and omitted values are malformed startup invocations.

--param binds only parameters declared by the selected entry pattern. An unknown name is a startup error after the pattern is resolved, and no run directory or run identity is created from that invocation. The same parameter may be supplied at most once; repetition through two --param occurrences, or through both a selector-bound value and --param, is a startup error. Omitted parameters use their declared default; an omitted parameter without a default is a startup error. After startup every entry parameter therefore has exactly one bound value, chosen in this order: fixed selector binding (--map / --until) or --pattern positional binding, then --param, then the declared default. A value supplied earlier in that order cannot be overridden later.

on_stop, when present, installs hooks for engine stops. A single pattern value is shorthand for the any catch-all:

toml
on_stop = "write_handover"

A hook table keys handlers by stop class:

toml
[header.on_stop]
step = "repair_unrouted_step"
gate = "repair_gate_stop"
any = "write_handover"

The hook-table keys are the stop classes step, absence, plan, gate, and record, plus the catch-all key any. A class-specific hook takes precedence over any. any is not a stop class; it is only the hook-table catch-all key. record is accepted so record-class stops remain representable in the same table, but record hooks never dispatch and record stops never fall through to any. merge is a stop class but is not a hook-table key, and merge stops do not fall through to any.

Engine stop classes are closed:

Class Meaning
step A producing step ended as failed and no pattern route handled that outcome.
absence A required branch or runtime binding was absent and no pattern route handled that absence.
plan Plan handling could not produce executable work, including a planning result that produced no work.
gate A gate stop ordinary gate routing did not handle, including a baseline or admission gate that failed before authored recovery could run.
record The run record cannot safely support execution or resume under the active interpreter contract.
merge Merge or merge-repair work could not be completed. Not accepted as an on_stop hook key.

When an engine stop has a matching hook, the engine runs that hook before the stop is final. The hook receives the stop facts through prose placeholders and environment data. After the hook finishes, the engine re-derives the stop condition. If the condition has cleared, execution continues. If the same stop class recurs at the same position with no new witnessed boundary since the hook ran, the engine records a final stop using the re-derived facts. That recurrence rule stops a hook looping on one condition without blocking a hook that makes progress and exposes a new boundary.

Authored stop steps, operator pauses, and operator aborts are already terminal and do not enter on_stop.

4. Patterns, Parameters, And Levels

A [patterns.<name>] table defines one callable pattern. It may contain parameters and steps:

toml
[patterns.task]

[[patterns.task.parameters]]
name = "worker_prompt"
kind = "prompt-id"
default = "execute"

[[patterns.task.steps]]
name = "build_task"
type = "agent"
prompt = "{{worker_prompt}}"

The allowed pattern keys are:

Key Meaning
parameters Ordered input declarations for this pattern.
steps Ordered step declarations.

There is no level key. A for or a call opens a depth in the task tree, and the depth is read from the composed task path: the interface renders a position as numbers (4/7 flat, 2/5:3/7 nested), so nothing consumes a label.

Every run that executes this format records its run shape in durable state. That record identifies the run as invocation-scoped, stores the task-list directory declared for each container task path, and lets a cold reader resolve a task invocation's state root from its full task path, with called-pattern frames stored under their own invocation namespace. Ledger rows for such runs key work by that full task path (01/02/01), not by the final segment alone; historical single-namespace records are compatibility records and must be classified before they are translated or refused.

Parameters are the only author-declared input slots:

Parameter key Required Meaning
name yes Identifier used by whole-value references such as {{plan_source}}.
kind yes One of the closed parameter kinds below.
default no Value used when the caller or CLI omits the parameter.
declared_plan_source no Marks the single input that supplies run identity and source-plan recovery.
description no Human-facing documentation.

Parameter names must be non-empty, unique within the pattern, and must not be reserved names. declared_plan_source = true may appear on at most one parameter in the entry pattern reached by a run.

The closed parameter kind vocabulary is:

Kind Meaning
path A file or directory path frozen or resolved as an input value.
prompt-id A prompt identifier resolved through Gantry's prompt tiers.
text Authored prose passed into prompt text or notes.

No parameter kind may introduce a computed identifier. A parameter value may fill a slot that is already typed as a prompt id, path, command path, or prose value; it may not supply a pattern name, step name, output name, branch target, parameter name, or other namespace identifier.

5. Names And References

The loader resolves every author-owned name before execution:

Namespace Declared by Referenced by
Pattern names [patterns.<name>] entry, merge, on_stop, body, pattern, branch targets.
Parameter names [[patterns.<name>.parameters]] Whole-value {{parameter}} references in that invocation.
Step names name on a producing step Step-outcome tests and { before = "<step>" } boundary references.
Output names outputs = [{ name = ... }] if = "<output>" and whole-value {{output}} references.
Task references The active for iteration {{task}}, {{task.name}}, {{task.prompt}}, {{task.gate}}.

Step names and output names are declared once across the whole composed definition, not merely within one pattern; a second declaration of either, in any pattern, is a load error.

Forward references are legal: a step may read an output declared by a later step when recursion or a prior invocation can make that output present. Forward references still resolve at load; only the file's runtime presence is deferred.

Whole-value references use {{name}} spelling only when the entire TOML string is the reference, except in prose strings and declared output paths. They do not substitute into command text.

The closed context reference vocabulary is:

Reference Meaning
{{parameter}} The current invocation's bound parameter value.
{{output}} The current readable binding for a declared output.
{{task}} The active task file path inside a for body.
{{task.name}} The active task file basename, without directory path.
{{task.prompt}} The prose body carried by the active task file, usable as an agent prompt slot.
{{task.gate}} The gate command carried by the active task file, usable as a gate command slot.
{{STOP_REASON}} Prose facts for an on_stop hook.

{{task.name}} may appear inside a declared output path to make one run-scoped path per task. Otherwise references do not produce identifiers.

The engine exports command data as environment variables rather than prose substitution. Command steps, and a gate step's command, receive declared parameters, readable outputs, and task references as whole-value environment variables. A readable output with a declared value vocabulary is exported as that value. A readable valueless file output is exported as its file text. A readable directory output is exported as its declared path. The current step's own declared outputs are exported as absolute paths so the process can write them. When an output reference fills a typed path slot such as an agent input, agent protected path, agent judging gate, gate command, or for directory, it resolves to the output path. Dots become underscores: {{task.name}} is available as $task_name, {{task.gate}} as $task_gate. The orchestration directory is $orchestration. Command text containing {{...}} is a load error unless the whole field is a typed slot such as command = "{{task.gate}}".

6. Producing Steps

Every step may declare a title. The value is literal display text: the loader does not substitute or infer it, and an empty string is accepted. Every producing step may declare a name, outputs, and step-specific properties. A step that declares outputs must also declare a name. A named step also implicitly produces a branchable outcome with the closed values done and failed. done means the engine accepted the step boundary and all required file contracts for that step were satisfied. failed means the step did not complete as an accepted boundary: the session died, a required output was malformed or missing, a command failed in a context that treats failure as the outcome, a gate was red when tested through outcome routing, or the engine could not finalize the boundary.

Malformed authored data is failure, not a synthesized value. A missing output is absence, not failure, when the producing step itself completed and the output is not required for that step's outcome. Branching keeps these facts distinct:

Fact Routed by
A produced value if = "output_name" with a then table.
One specific produced value if = { output_name = "value" }.
An absent output else on that if.
A failed step if = { step_name = "failed" }.

summarize is a key of the agent and gate step kinds only, because those are the two kinds the shipped prompt corpus has a summarizer prompt for. On any other step kind it is an unknown key and a load error.

6.1 agent

An agent step runs one fresh agent session.

Key Required Meaning
type yes Must be "agent".
name when referenced Step identifier.
title no Literal display text.
stage no Closed stage label for the work this agent performs; omitted means execute.
summarize no Generated finish summary on the step's card; omitted means off.
prompt yes Prompt id or whole {{task.prompt}} / {{prompt-id-parameter}} slot.
inputs no Paths or output references shown to the agent.
outputs no File or directory contracts the agent may produce.
text no Prose placeholders injected into the prompt.
model no Per-step model override, as a non-empty string.
effort no Per-step effort override: low, medium, or high.
harness no Per-step harness override, as a registered harness id.
read_only no Snapshot and restore the tree after the session.
protected no Paths restored after the session.
confine no Detect edits outside the allowed worktree.
on_plan_change no Plan-change policy for observed plan-file edits.
diff no Boundary reference for the review span shown to the agent (§ 13).
judging_gate no Whole-value command slot for the gate expected to judge this step.

stage selects the work stage whose role is used for agent selection. The vocabulary is closed and spelled with Stage's machine labels: environment-build, plan, gate-build, execute, gate, fix, investigate, troubleshoot, review, replan, merge, resolve, support, and done. Omitted stage resolves to execute.

model, effort, and harness are per-step selection overrides, and an omitted override is not the same as an explicit value. The selection precedence is step override first, then the role resolved from the step's stage, then the global default. model is a non-empty harness-specific string. effort is the closed vocabulary low, medium, and high. harness must name a registered harness id.

prompt is resolved as a prompt id unless it is the whole {{task.prompt}} carried-prompt slot. A prompt id never selects a role, stage, branch, or recovery policy.

text is a table of prose placeholders:

toml
[patterns.recovery.steps.text]
POSITION_CLAUSE = "Repair the red gate and write the troubleshoot verdict."

Text keys are prompt placeholders. Their values are prose and may contain prose references such as {{STOP_REASON}} or parameter text. A placeholder missing from carried task prose is a no-op.

summarize = true asks for a generated finish summary on the step's card. A review-stage agent step is summarized by the summarize-review.md prompt, run over the agent's own output and the diff of what the review itself changed. An agent step in any other stage has no summarizer prompt that fits it, and declaring summarize there changes nothing about its card. An absent, failed, or empty summary is not a failure: the card carries its ordinary finish summary.

on_plan_change = "<handler>" names a pattern in the composed namespace. The engine observes declared plan-file changes across the agent step, calls the handler on an observed diff, and re-observes until the observation is clean. It is the policy for executor, reviewer, and recovery steps.

on_plan_change = "nothing" observes declared plan-file changes and accepts the observed diff without a handler. It is the policy for planner steps whose product is plan files: writing those files is the step's declared work, not an edit to plan files someone else owns.

An omitted on_plan_change key leaves the step unwatched for plan-file edits.

The watched set is engine-derived from the declared plan source and files derived from it; the format has no author-owned plan-change scope vocabulary.

judging_gate names the gate command expected to judge the agent's work. Omission is valid for an agent step not judged by a following gate. When present, the loader scans forward in the same step list, skips if steps, stops before another agent step, and compares it with the first following gate.command. A mismatch is a load error naming the agent step. Branch targets are not scanned.

6.2 gate

A gate step runs Gantry's authoritative project check package. The gate carries Gantry's baseline checks, red/green verdict handling, merge re-gates, and recovery semantics.

Key Required Meaning
type yes Must be "gate".
name when referenced Step identifier.
title no Literal display text.
command yes Whole-value command slot, most often {{task.gate}} or an output reference.
summarize no Generated finish summary on the step's card; omitted means off.
restore no Boundary reference restored before the gate runs.
outputs no Additional file contracts.

command is required. It is an output reference, task gate reference, or parameter reference. A gate step without command is a load error naming the gate step.

The branchable gate outcome vocabulary is closed:

Outcome Meaning
done The gate completed green.
failed The gate completed red or could not produce an authoritative verdict.

For the shorthand step-outcome form, a gate may also be tested by color:

toml
[[patterns.sprint.steps]]
if = { build_gate = "red" }
then = "recover_build"
else = "review_and_commit"

The closed gate color vocabulary is green and red. Infrastructure failure is not a third color; it reaches the step's failed outcome or the engine-forced stop according to the branch that exists.

summarize = true asks for a generated finish summary on the gate step's card, written by the summarize-gate.md prompt over the gate command the step ran, in place of the verdict word the card carries otherwise. An absent, failed, or empty summary is not a failure: the verdict word stands.

6.3 command

A command step runs deterministic process text owned by the pattern author.

Key Required Meaning
type yes Must be "command".
name when referenced Step identifier.
title no Literal display text.
run yes Command text executed with sh -c.
outputs no File or directory contracts the command writes.
restore no Boundary reference restored before the command runs.

Commands receive data through environment variables, not through {{...}} interpolation in run. The command's stdout and stderr are records. Control flow reads declared outputs or the step outcome, not prose emitted by the command unless the command itself writes that prose to a declared output.

A command in a deciding position writes a one-word output and an if reads that output; that is how the language expresses every numeric predicate, roster validation, goal check, and other deterministic decision procedure. Numbers are never stored as pattern state: a command may derive a count from the record or filesystem, reduce it to an authored value, write that value, and discard the count.

7. Outputs

An output is declared on the step that produces it:

toml
outputs = [
  { name = "review_verdict", path = "state/review-verdict.txt", values = ["complete", "retry", "blocked"] },
  { name = "handover_document", path = "state/handover.md" },
  { name = "roster", path = "state/roster/" },
]

Allowed output keys are:

Key Required Meaning
name yes Identifier for references and branch tests.
path yes Orchestration-relative file or directory path, or one whole-value reference such as "{{check}}".
values no Closed value vocabulary for branchable file outputs.
scope no Output lifetime, defaulting to the current invocation.

The output scope vocabulary is closed:

Scope Meaning
omitted Current invocation. The output belongs to that call or for iteration's state frame.
run Run-scoped. The output path is addressed from the run frame and survives the invocation.

A path ending in / declares a directory output, and a directory output never declares values. File outputs with values are branchable by if; file outputs without values are file contracts and prose or data records.

Every output declaration belongs to one producing step. A composed pattern may not declare one output name twice, and may not declare one path twice at one scope. Mutually exclusive branches still do not co-produce one path; the distinction goes in separate outputs or separate filenames.

Run-scoped paths may be produced inside a for body only when the path is distinct per iteration, for example by using {{task.name}}. A colliding run-scoped path is a load error. A self-recursive pattern may re-produce its own run-scoped output, last writer winning, which is how one recursive invocation passes a document to its successor.

An output with values must contain exactly one member of its declared vocabulary when present. Whitespace around the value is ignored. Unknown values are malformed output and therefore the producer's failed outcome, not absence and not a default.

8. if

An if step routes to another pattern. It has no type key.

There are three forms:

toml
[[patterns.review.steps]]
if = "review_verdict"
then = { complete = "verify_review_edits", retry = "retry_sprint", blocked = "stop_review_blocked" }
else = "stop_missing_review"

[[patterns.until.steps]]
if = { check_present = "yes" }
else = "author_check"

[[patterns.review.steps]]
if = { review_gate = "red" }
then = "repair_review"
else = "nothing"

The first form branches on a declared output. The then table must match the output's declared values vocabulary exactly: every declared value appears exactly once, and no other key appears. The optional else arm handles absence: the file was not present in the current binding. Absence is never a value, never a malformed record, and never an implicit fall-through.

The second form tests a declared output for one value of its declared values vocabulary; a value outside that vocabulary is a load error. When the output holds the value, the single-pattern then target runs; when it holds another value or is absent, else runs, or with no else execution continues at the next step.

The third form branches on a producing step outcome. The closed step-outcome vocabulary is:

Outcome Meaning
done The step's accepted boundary exists and its required contracts are satisfied.
failed The step did not produce an accepted boundary or produced malformed required data.

For gates, the color aliases green and red are accepted in this form, with the meaning in § 6.2.

else on a step-outcome if handles the outcome not named by then. With no else, a non-matching done outcome continues at the next step; a failed outcome left unrouted reaches the engine-forced stop, and the step immediately after a failed producing step must be an if testing that step. A step-outcome if with no then is a load error, and so is an if with neither then nor else.

Only one fact may be tested by one if. Boolean operators, numeric comparisons, list-empty checks, flag checks, and compound conditions are not part of the language. A pattern that needs such a decision uses a command to write a one-word output, then routes on that output.

Branch targets name patterns. success, complete, rollback, retry, handover, quiesce, and other rule outcomes of the retired format are not reserved branch words. A file using one of those spellings as a branch target must define a pattern with that name, and a reserved name cannot be defined.

Allowed keys are:

Key Required Meaning
title no Literal display text.
if yes Condition form.
then when needed Branch target or value table.
else no Absence or non-match branch target.

9. for

A for step runs a body pattern once for every task in a list. It is sequential: it is not a parallel map and carries no concurrency property.

toml
[[patterns.run_roster.steps]]
name = "run_tasks"
type = "for"
list = { directory = "{{roster}}" }
body = "task"
fail = "continue"

[patterns.run_roster.steps.parameters]
source = "{{task}}"

Allowed keys are:

Key Required Meaning
type yes Must be "for".
name when referenced Step identifier.
title no Literal display text.
list yes List reference.
body yes Pattern called once per task.
parameters no Parameter bindings for each body invocation.
fail no Handling for an unrouted failed body invocation.

The closed list-reference vocabulary has one form:

Form Meaning
{ directory = "<path-or-output-reference>" } Enumerate the files in that directory in sorted order.

Every list is a directory of task files, whether a previous step produced it or it was supplied as an input. The directory is re-read before each iteration, so work inserted by earlier iterations can still be reached. Before an iteration starts, the engine consults the invocation's durable ledger and omits only tasks whose full task path has latest status DONE; rows with any other status, malformed rows, missing rows, and a sibling that merely shares the same final task id do not skip the body. After checkpoint restore no step's recorded result is reused: a resumed for omits tasks whose latest ledger row is DONE, and every other step of the resumed invocation executes again, so a command that ran before the stop can run again.

A task is the file currently selected by the list. The task reference vocabulary is closed: {{task}}, {{task.name}}, {{task.prompt}}, and {{task.gate}}. A task file that carries prompt or gate data must use the task-file format the body expects; malformed task data is a failed body invocation.

The for.fail vocabulary is closed:

Value Meaning
stop Default. An unrouted failed body invocation forces the run to stop.
continue Record the failure and continue to the next task.

fail handles only what the body did not route: a dead session, missing required boundary, unrouted failed gate, or other failed body invocation. A red gate that the body routes to repair and then to a task verdict is not a for.fail event.

fail governs a failed iteration and never an authored or engine stop. A stop reached anywhere — including inside a for body declaring fail = "continue" — ends the whole run, and no composition operator interprets it or continues past it. An engine stop reached inside the body follows the run-level on_stop contract and is not converted into a continued iteration by fail = "continue".

When a body invocation falls off its end, that task is complete. No pattern declares a commit, DONE-row, ledger, or task-completion operation; recording task completion is part of interpreting for.

10. call

A call step invokes another pattern in the same composed namespace.

toml
[[patterns.retry_sprint.steps]]
name = "retry_sprint"
type = "call"
pattern = "sprint"
restore = "iteration-start"

[patterns.retry_sprint.steps.parameters]
execute_prompt = "{{execute_prompt}}"

Allowed keys are:

Key Required Meaning
type yes Must be "call".
name when referenced Step identifier.
title no Literal display text.
pattern yes Callee pattern name.
parameters no Callee parameter bindings.
restore no Boundary reference restored before the call runs.

Calls open a fresh invocation state directory. Outputs from the callee remain readable in the caller after a non-tail return. A call rebinds every output declared by its callee on every invocation: if the callee does not produce an output on the second call, the caller sees that output as absent for the second call, not as the first call's stale value.

A tail call never returns: it replaces the current continuation, so recursion does not grow the invocation stack.

The format composes definitions into one namespace through include, and within that namespace outputs are readable according to invocation state. A call to a pattern outside the composed namespace is not a construct the language has.

11. Recursion

Recursion is the only unbounded repetition the language has. There is no repeat, until, loop, cycle count, retry budget, depth guard, or max_cycles pattern word. A recursive run ends when ordinary data routes to a pattern that falls off its end or to stop. An unattended recursive run is unbounded; operators bound it with lifecycle controls or with authored entry parameters such as --param max_cycles 3 when the selected pattern declares and consumes such a parameter. The bundled until pattern declares max_cycles with the default unbounded; a command step counts completed build cycles and writes a decision output, and an if on that output reaches an authored stop at the ceiling. The bundled sprint pattern declares attempt_ceiling with the default unbounded; the build entry passes 2, and the same command-and-if shape stops retry recursion after the second failed attempt.

The loader must detect the call graph and validate tail position for every call that participates in a cycle. A recursive call is valid only when every path from the enclosing pattern's entry to that call reaches it in tail position: no later step in the caller can run after the recursive call returns. Without that rule, a recursive retry could unwind into stale post-retry work.

Recursive invocations get fresh per-invocation state, so attempt one's outputs are not readable in attempt two. Run-scoped outputs are the only values that survive across recursive invocations, and their collision rules are in § 7.

12. stop

A stop step ends the run with one authored note:

toml
[[patterns.stop_no_gap_left.steps]]
type = "stop"
note = "The acceptance check is still red and the planner found no actionable work left."

Allowed keys are:

Key Required Meaning
type yes Must be "stop".
title no Literal display text.
note yes Human-facing explanation of why the pattern has no next move.

There is no stop disposition vocabulary, report field, reason enum, secondary stop word, success word, or operator-decision surface. Documents an agent wrote before the stop are already records, and the note names the paths worth inspecting. Falling off the end of the entry pattern is ordinary completion. Falling off a non-entry called pattern returns to its caller unless the call was tail-position recursion.

The pattern language does not model operator action. A stopped run records the prefix Gantry witnessed, and resume applies the same frozen pattern again from the last recorded boundary; neither records what the operator did while the run was stopped.

13. Boundary References

restore and review diff properties name structural commit boundaries. They never name a Git revision and never run Git operations as pattern steps.

The closed boundary-reference vocabulary is:

Reference Meaning
run-base The run's starting project commit.
iteration-start The start boundary of the nearest dynamically enclosing for iteration.
previous-step The accepted boundary before the current step.
{ before = "<step name>" } The accepted boundary immediately before the named step ran.

Boundary references resolve dynamically against the invocation and iteration stack. For example, a review after an inner for has ended and inside an outer for sees iteration-start as the outer iteration's start.

restore may appear on gate, command, and call steps. It tells the engine to place the tree at the referenced boundary before the step runs. diff may appear on agent steps that need a review span; it controls prompt data, not execution dispatch.

14. Secondary Vocabularies

Primary words: pattern, agent, gate, command, for, if, stop, call, output.

Step type values: agent, gate, command, for, call, stop. An if step is recognized by its if key and has no type.

Parameter kinds: path, prompt-id, text.

Output scopes: omitted invocation scope, run.

List references: { directory = ... }.

Task references: {{task}}, {{task.name}}, {{task.prompt}}, {{task.gate}}.

Context/prose references: declared parameter references, declared output references, task references, {{STOP_REASON}}. Command environments expose the same data as environment variables, with dots changed to underscores.

Step outcomes: done, failed.

Gate color aliases: green, red.

for.fail values: stop, continue.

Boundary references: run-base, iteration-start, previous-step, { before = "<step name>" }.

Stop classes: step, absence, plan, gate, record, merge. any is only an on_stop hook-table catch-all key; it is not a stop class. record and merge hooks never dispatch and neither class falls through to any.

Reserved names: nothing, the primary words, step outcome words, gate color aliases, continue, run, task, task.name, task.prompt, task.gate, STOP_REASON.

Agent step properties: name, title, type, stage, summarize, prompt, inputs, outputs, text, model, effort, harness, read_only, protected, confine, on_plan_change, diff, judging_gate.

Agent stages: environment-build, plan, gate-build, execute, gate, fix, investigate, troubleshoot, review, replan, merge, resolve, support, done.

Agent effort values: low, medium, high.

Agent harness values: registered harness ids.

Gate step properties: name, title, type, summarize, command, outputs, restore.

Command step properties: name, title, type, run, outputs, restore.

For step properties: name, title, type, list, body, parameters, fail.

If step properties: title, if, then, else.

Call step properties: name, title, type, pattern, parameters, restore.

Stop step properties: title, type, note.

Output properties: name, path, values, scope.

Header properties: pattern_language_compatibility, entry, description, include, merge, on_stop, default, cli_flags.

Pattern properties: parameters, steps.

Parameter properties: name, kind, default, declared_plan_source, description.

Condition forms: if = "<output>", if = { <output> = "<value>" }, and if = { <step-name> = "<outcome-or-gate-color>" }.

Branch table forms: then = "<pattern>", then = { <value> = "<pattern>", ... }, and else = "<pattern>".

The retired format's vocabularies have no meaning here: counter resets and counter signals, condition kinds, rule outcomes, failure policies, channel producers and degrade defaults, task completion records, work-list source kinds other than directory, finalizers, side loops, git ops, terminate dispositions, goal state qualifiers, script branch tests, plan-change scope tables, inner-unit operations, and sub-pattern outcomes.

15. Compatibility

The reader does not accept the retired format's headers, blocks, rule blocks, counters, channels, inner-unit contracts, or old step kinds. Obsolete keys are rejected rather than treated as aliases.

Gantry may still need to inspect, resume, display, or explain historical runs whose pins, ledgers, channels, or journals were written by older engines. That support belongs to historical record readers and record compatibility. It must not add accepted words to the language, must not make retired-format files load through the reader, and must not let a frozen run's pin marker be confused with the source file's header declaration.

The practical rule: pattern-language compatibility decides whether a source file may be read and executed by the interpreter; record compatibility decides whether Gantry can still read what older interpreters already wrote.

docs/patterns/spec.md in the Gantry repository, rendered as it stands