skip to content

The sprint pattern

One sprint's gated pipeline: execute → gate → one bounded fix → re-gate → review → re-gate → post-review repair-or-revert → commit. Composed into `milestone`'s sprint loop by an `include` step — material for an includer, never selected or run on its own.

sprint — one sprint's gated pipeline

execute → gate → one bounded fix → re-gate → review → re-gate → post-review repair-or-revert → commit. Every code-mutating agent stage is followed by a gate, and every gate that goes red gets one bounded fix.

This is the body milestone.toml composes into its sprint loop with an include step, and it is included_only because that is exactly what it is: material for an includer, not a pattern a run starts. Its red-gate exits branch into escalation entries the milestone file defines, so it never validates alone. Together the two files are the whole statement of the compiled per-sprint pipeline Gantry ran before patterns became first class.

sprint
[header]
name = "sprint"
version = "1.5"
description = """
One sprint's gated pipeline: execute → gate → one bounded fix → re-gate → review → re-gate →
post-review repair-or-revert → commit. Composed into `milestone`'s sprint loop by an `include` step —
material for an includer, never selected or run on its own.
"""
entry = "build_task"
included_only = true

Only what this pipeline actually reads. The plan source and the baseline-relaxed flag belong to the milestone file's planning half, which declares them and passes these two through to its inclusion.

sprint
[[header.parameters]]
name = "prompt_prefix"
kind = "string"
default = ""
description = "Optional prefix for prompt ids this shared pipeline resolves, e.g. design/briefs/."

[[header.parameters]]
name = "review_prompt"
kind = "string"
default = "review"
description = "Prompt id for the review stage; pass-specific sprint pipelines may provide their own."

The inner-unit state contract

The machine-readable files and durable completion effect the production sprint world consumes. Paths are relative to this pattern instance's orchestration directory, so sub-pattern invocations read and write their nested unit's state rather than repository-root paths.

sprint
[inner_unit_state]
completion = { commit_block = "commit_task", record = "commit-and-ledger-done", idempotent_green_noop = true }

The whole contract lives here because a composed pattern admits at most one — two files declaring one is a refusal — and it belongs with the completion contract, which is this file's: commit_task records a sprint DONE. That is also why the three troubleshoot channels are declared below even though the ladder producing and reading them is the milestone's. They are per-sprint-attempt state, durably retired the moment a sprint goes DONE (retire_completed_task_channels in src/engine/run/pattern_interpreter.rs), while pattern-level [[channels]] are left alone there.

The reviewer's scope verdict. The degrade default of complete is the legacy executor's silent scope-complete-on-parse-failure bias made explicit and forkable: a careful fork writes retry here.

sprint
[[inner_unit_state.channels]]
name = "review_scope"
path = "state/review-scope.toml"
producer = "{{review_prompt}}"
values = ["complete", "retry", "blocked"]
degrade_default = "complete"

[[inner_unit_state.channels]]
name = "retry_note"
path = "state/retry-note.md"
producer = "{{review_prompt}}"

An executor or fixer that cannot proceed says so here, and the run stops rather than gating noise.

sprint
[[inner_unit_state.channels]]
name = "executor_blocked"
path = "state/executor-blocked.toml"
producer = "{{prompt_prefix}}execute"
values = ["blocked"]

[[inner_unit_state.channels]]
name = "executor_blocked_note"
path = "state/executor-blocked-note.md"
producer = "{{prompt_prefix}}execute"

[[inner_unit_state.channels]]
name = "fixer_blocked"
path = "state/fixer-blocked.toml"
producer = "fix"
values = ["blocked"]

[[inner_unit_state.channels]]
name = "fixer_blocked_note"
path = "state/fixer-blocked-note.md"
producer = "fix"

The post-review fixer's revert verdict. Only repair_review is ever told about this channel: revert means review's own edits — the provably isolated cause of a post-review red — are cosmetic damage, so Gantry restores the pre-review mark instead of fixing forward. Every other fix leaves the file absent, and the producing prompt's channel clearing removes a stale verdict before each fix.

sprint
[[inner_unit_state.channels]]
name = "review_repair"
path = "state/review-repair.toml"
producer = "fix"
values = ["revert"]

The troubleshoot agent's declared outcomes, written only when the repair could not be verified: retry asks for a briefed rollback-and-retry (the note reaches the next execute through prepare-retry), handover stops the unit with the document as the stop artifact. A verified repair writes neither — the gate, not the agent's word, is what advances the run. The handover channel's required first line (<split|replan|flaky|contract|other> — <one sentence>) keeps the machine-readable summary the old investigation_recommendation carried; the rest is the document the next reader starts from.

sprint
[[inner_unit_state.channels]]
name = "troubleshoot_disposition"
path = "state/troubleshoot-disposition.toml"
producer = "troubleshoot"
values = ["retry", "handover"]
degrade_default = "handover"

[[inner_unit_state.channels]]
name = "troubleshoot_note"
path = "state/troubleshoot-note.md"
producer = "troubleshoot"

[[inner_unit_state.channels]]
name = "handover"
path = "state/handover.md"
producer = "troubleshoot"

build_task

Execute the sprint — fall-through, because the gate that follows renders the verdict on whatever it did to the tree, in both directions. The mutating stages protect sprint briefs and run-control artifacts relative to this child orchestration, and confinement-check unsandboxed runs for writes that escaped the intended worktree.

sprint
[[blocks.build_task]]
type = "agent"
role = "build"
prompt = "{{prompt_prefix}}execute"
failure = "fall-through"
protected = ["PROGRESS.md", ".preamble.sys.md", "bin", ":(glob)[0-9][0-9]-*.md"]
confine = true

[[blocks.build_task]]
type = "conditional"
condition = { kind = "channel-value", channel = "executor_blocked", value = "blocked" }
then = "executor_blocked_stop"

[[blocks.build_task]]
type = "gate"
if = "red"
then = "repair"
else = "after_green_gate"

On green — initially or after the bounded fix — an unchanged tree records a no-op DONE and skips review and commit; otherwise review and commit.

sprint
[[blocks.after_green_gate]]
type = "conditional"
condition = { kind = "inner-unit-green-noop" }
then = "record_noop_done"

[[blocks.after_green_gate]]
type = "conditional"
condition = { kind = "flag", value = "true" }
then = "finish_task"

repair

The one bounded fix: a single repair agent, then the re-gate. It inherits execute's protected-brief, protected-run-control and confinement contract. The empty situation clause keeps the shared fix.md on its plain repair reading — only repair_review fills the clause, with the revert option.

sprint
[[blocks.repair]]
type = "agent"
role = "build"
prompt = "fix"
stage = "fix"
failure = "fall-through"
protected = ["PROGRESS.md", ".preamble.sys.md", "bin", ":(glob)[0-9][0-9]-*.md"]
confine = true
template_values = { SITUATION_CLAUSE = "" }

[[blocks.repair]]
type = "inner-unit"
op = "note-channel"
channel = "fixer_blocked"

The red-gate exit into the milestone file's escalation entry — half of the mutual contract that file states.

sprint
[[blocks.repair]]
type = "gate"
if = "green"
then = "after_green_gate"
else = "troubleshoot_or_handover"

finish_task

The mark records the pre-review working tree — the gate just proved it green — so the post-review fixer's revert option has a recorded point to restore. Marks are per-sprint-attempt state and never survive a rollback or the sprint's completion.

sprint
[[blocks.finish_task]]
type = "git"
op = "mark"
name = "pre_review"

Review, which may edit. It carries the same protected and confinement contract as execute and fix. Protected sprint-plan edits are withheld as a draft: started and completed briefs route to the frozen-unit violation halt, while future-brief proposals go to the declared replanner. The fixed-point handler is deliberate — if the replanner changes plan files too, Gantry observes again and repeats until the plan stops changing, bounded by spend guards rather than a loop counter.

sprint
[[blocks.finish_task]]
type = "agent"
role = "review"
prompt = "{{review_prompt}}"
failure = "degrade"
degrade_default = "complete"
protected = ["PROGRESS.md", ".preamble.sys.md", "bin", ":(glob)[0-9][0-9]-*.md"]
protected_draft = { path = "state/review-plan-draft.diff" }
observed_plan_change = { changed = "replan_remaining_tasks_from_review_draft", unchanged = "verify_review_edits", source_plan_violation = "failure", frozen_unit_violation = "failure", handler = { kind = "fixed-point", block = "replan_remaining_tasks_from_review_draft" } }
confine = true
template_values = { REVIEW_SUBJECT = "sprint", ALTITUDE_CLAUSE = "Review altitude: sprint. The unit being reviewed is one sprint. You may edit the working tree to make safe improvements. The remaining plan means the later, not-yet-built sprints in this same sprint plan. Express any required brief changes by editing the scoped plan files; protected future-brief edits are withheld as a draft and routed through the observed plan-change handler." }

Re-gate review's edits. A review that edits and leaves the gate red gets one bounded fix of its own before the attempt is discarded into the milestone's recovery rules.

sprint
[[blocks.verify_review_edits]]
type = "gate"
if = "green"
then = "after_green_review"
else = "after_red_review_gate"

[[blocks.after_red_review_gate]]
type = "conditional"
condition = { kind = "channel-value", channel = "review_scope", value = "blocked" }
then = "review_blocked_stop"

[[blocks.after_red_review_gate]]
type = "conditional"
condition = { kind = "flag", value = "true" }
then = "repair_review"

repair_review

Review's own edits turned a green gate red: give them the same bounded fix a red-after-execute gets, rather than discarding the executor's working code along with them. This is one fix agent per gate, not per sprint — the milestone file's recovery_attempts budget is what bounds the sprint.

This fix alone holds the revert option: the gate was green before review ran, so review's diff is the bisect. Writing value = "revert" routes to revert_review_edits instead of the fix-forward re-gate.

sprint
[[blocks.repair_review]]
type = "agent"
role = "build"
prompt = "fix"
stage = "fix"
failure = "fall-through"
protected = ["PROGRESS.md", ".preamble.sys.md", "bin", ":(glob)[0-9][0-9]-*.md"]
confine = true

The revert file's path is written into the clause directly, with $orchestration expanded by the engine, rather than through a second {{REVERT_PATH}} variable: template values fill in one alphabetical pass, so a variable referenced from inside another value's text would be filled before the clause carrying it is inserted, and would survive as a literal marker.

sprint
[blocks.repair_review.template_values]
SITUATION_CLAUSE = """
Situation: the gate was green before a review agent edited this tree, so the review's own edits are
the sole cause of the red — the diff below ends with them. Decide which kind of edit broke it. If the
breaking edits are cosmetic, refactoring, or style changes, do not repair them: write the file
`$orchestration/state/review-repair.toml` as TOML containing exactly the line `value = "revert"` and
stop — gantry will restore the pre-review tree, keeping the executor's work. If an edit strengthened
a check, corrected a wrong assertion, or surfaced a genuine defect, the red is information, not
damage: fix forward as described below and leave that file absent. The review's own written
rationale, visible in the diff and its notes, is available to decide."""

[[blocks.repair_review]]
type = "inner-unit"
op = "note-channel"
channel = "fixer_blocked"

The fixer chose revert: restore the pre-review mark rather than judging the fixer's (non-)edits.

sprint
[[blocks.repair_review]]
type = "conditional"
condition = { kind = "channel-value", channel = "review_repair", value = "revert" }
then = "revert_review_edits"

The post-review red-gate exit into the milestone file's _review escalation entry — the other half of the mutual contract. A green repair rejoins the ordinary post-review routing, so review's scope verdict still decides the sprint's fate.

sprint
[[blocks.repair_review]]
type = "gate"
if = "green"
then = "after_green_review"
else = "troubleshoot_or_handover_review"

revert_review_edits

Put the project tree back to the recorded pre-review point, preserving the orchestration directory exactly like a project-only reset, then verify with the re-gate — green by construction, but nothing untested reaches a commit. A red re-gate means the restored tree is not what the mark promised, and it falls into the same review troubleshoot entry as a failed fix-forward.

sprint
[[blocks.revert_review_edits]]
type = "git"
op = "reset"
scope = { kind = "to-mark", mark = "pre_review" }

[[blocks.revert_review_edits]]
type = "gate"
if = "green"
then = "after_green_review"
else = "troubleshoot_or_handover_review"

The reviewer's scope verdict decides the sprint's fate on a green post-review gate.

sprint
[[blocks.after_green_review]]
type = "conditional"
condition = { kind = "channel-value", channel = "review_scope", value = "blocked" }
then = "review_blocked_stop"

[[blocks.after_green_review]]
type = "conditional"
condition = { kind = "channel-value", channel = "review_scope", value = "complete" }
then = "commit_task"

Commit the sprint's work, marking it done so the milestone loop advances. The completion contract above names this block.

sprint
[[blocks.commit_task]]
type = "inner-unit"
op = "commit-task"
note = "task complete"

An unchanged tree with a green gate records the sprint DONE without review or a new work commit.

sprint
[[blocks.record_noop_done]]
type = "inner-unit"
op = "record-noop-done"
note = "already satisfied; gate green; recorded done"

[[blocks.record_noop_done]]
type = "conditional"
condition = { kind = "flag", value = "true" }
then = "continue"

The blocked stops

review_blocked_stop is reached from this file's post-review routing and from the milestone's recovery rules alike.

sprint
[[blocks.executor_blocked_stop]]
type = "inner-unit"
op = "stop-blocked"
channel = "executor_blocked"
note_channel = "executor_blocked_note"

[[blocks.review_blocked_stop]]
type = "inner-unit"
op = "stop-blocked"
channel = "review_scope"
note_channel = "retry_note"

replan_remaining_tasks_from_review_draft

Observed future-brief edits from review are proposal-style: the protected files were restored, the retained draft is supplied to the same sprint planner, and the fixed-point observation decides whether another handler round is needed before the review's code edits are verified.

sprint
[[blocks.replan_remaining_tasks_from_review_draft]]
type = "agent"
role = "plan"
prompt = "{{prompt_prefix}}write-sprints"
inputs = ["$work-list.future"]
withheld_draft = "state/review-plan-draft.diff"
failure = "halt"

[[blocks.replan_remaining_tasks_from_review_draft]]
type = "git"
op = "commit"