skip to content

The build pattern

The default milestone build: decompose a plan into ordered milestone briefs, then build each milestone as the shared milestone unit. Use for an ambitious plan larger than one work unit; the omakase route a flagless `gantry <plan>` takes.

build — the default milestone campaign

What a flagless gantry <plan> runs: a planner writes a directory of milestone briefs, then a loop builds each brief through the shared milestone sub-pattern, reviewing and optionally re-planning between them. Build is itself invocable whole — goal reuses it as its cycle body — so plan_source is its input contract and empty-campaign is a first-class outcome a parent branches on through sub-pattern-outcome.

As-built reference: run_whole_build_with_world in src/engine/run/driver_milestone.rs.

build
[header]
name = "build"
version = "1.2"
description = """
The default milestone build: decompose a plan into ordered milestone briefs, then build each milestone
as the shared milestone unit. Use for an ambitious
plan larger than one work unit; the omakase route a flagless `gantry <plan>` takes.
"""
entry = "main"
display_flat_when_single_top_level = true

Both levels are declared up front, not the milestone alone: a run whose milestones are planned but not yet decomposed is then already known to be two-level. Declaring only milestone left that window looking flat, and the TUI sidebar drew the run title as a phantom milestone with the real milestones nested under it. The leaf noun sprint matches the rows the milestone unit supplies, so the two declarations dedup to one ["milestone", "sprint"] list rather than a spurious third level. Single-milestone flatness is display behaviour only.

build
[[header.levels]]
label = "milestone"

[[header.levels]]
label = "sprint"

Build's whole input contract: the run's own plan for a top-level build, goal's per-cycle plan when build runs as a sub-pattern.

build
[[header.parameters]]
name = "plan_source"
kind = "string"
default = "plan.md"
declared_plan_source = true
description = "The plan prose or source path the milestone planner reads (the run's plan for a top-level build; goal's per-cycle plan when build runs as a sub-pattern)."

The milestone reviewer's verdict. The degrade_default also covers a review agent that failed outright.

build
[[channels]]
name = "milestone_review"
path = "state/milestone-review.toml"
producer = "review"
values = ["continue", "halt"]
degrade_default = "continue"

main

plan → about card → commit the briefs → empty campaign or full campaign.

The planner. halt: everything downstream builds on the milestone split. It carries no file contract — an empty directory is the declared empty-campaign outcome below, not a halt. On resume the briefs are already on disk and the production world skips this step rather than re-decomposing.

build
[[blocks.main]]
type = "agent"
role = "plan"
prompt = "write-milestones"
inputs = ["{{plan_source}}"]
failure = "halt"
skip_when_work_list_pre_populated = { source = { kind = "directory", path = "milestones" } }
halt_message = "directory campaign planner stopped the run"
template_values = { ORCH = "$orchestration/milestones" }

The about card is an ordinary agent-authored artifact. The roster announcement below only reads the persisted sidecar; it never dispatches a hidden decoration agent.

build
[[blocks.main]]
type = "agent"
role = "util"
prompt = "write-about"
inputs = ["$plan.name", "$work-list.roster:milestones"]
produces = ["about.md"]
read_only = true
failure = "note-and-continue"

Commit the briefs as an audit artifact before building — a no-op on resume, when the planner was skipped and nothing is staged.

build
[[blocks.main]]
type = "git"
op = "commit"
commit_fallback = "gantry: milestones {{PLAN_NAME}}"
commit_log_base = "milestones-commitmsg"
skip_when_work_list_pre_populated = { source = { kind = "directory", path = "milestones" } }
skip_when_work_list_empty = { source = { kind = "directory", path = "milestones" } }

Branch on the same work-list source the loop will consume, so an empty directory takes the declared empty-campaign outcome instead of entering a loop with nothing to build.

build
[[blocks.main]]
type = "conditional"
condition = { kind = "work-list-empty", source = { kind = "directory", path = "milestones" } }
then = "empty_campaign"
else = "build_campaign"

build_campaign

The roster is announced before the loop that consumes it, so it names its list explicitly — there is no active work-list context to inherit. Same source as the loop below and the branch above.

build
[[blocks.build_campaign]]
type = "inner-unit"
op = "announce-roster"
work_list = { source = { kind = "directory", path = "milestones" } }

The campaign loop, re-enumerated after every iteration so a review's future-brief edits are built before the loop reaches them. The survival invariants ride the directory source's defaults: a DONE milestone vanishing is a corrupt-plan halt, inserted milestones are announced, and the resume position is recomputed as first-not-DONE each pass.

build
[[blocks.build_campaign]]
type = "loop"
body = "build_one_milestone"
rules = "campaign_ending"
work_list = { source = { kind = "directory", path = "milestones" } }

build_one_milestone

One milestone, run as the shared unit and wrapped in the same bookkeeping envelope regardless of campaign length. The engine re-points the nested unit's orchestration, ledger and preamble per milestone; a single-milestone run uses the same nested ledger and only presents flat in displays.

build
[[blocks.build_one_milestone]]
type = "git"
op = "capture-head"

[[blocks.build_one_milestone]]
type = "sub-pattern"
pattern = "milestone"
parameters = { plan_source = "$work-list.entry" }

The milestone review may improve the tree, but its edits are verified before the milestone is recorded DONE. It declares no required output on purpose: a declared output is a hard file contract the interpreter halts on, and this review is advisory — no verdict means "continue".

protected covers the campaign's own briefs. Edits to future briefs are withheld as a draft and routed through the replanner below; edits to started or completed briefs are frozen-unit violations. The handler is a fixed point: if the replanner also changes plan files, Gantry observes again and repeats until the plan stops changing. Spend guards bound the pathological case — a hard counter would risk accepting a still-changing plan.

build
[[blocks.build_one_milestone]]
type = "agent"
role = "review"
prompt = "review"
protected = ["milestones"]
protected_draft = { path = "state/milestone-review-plan-draft.diff" }
observed_plan_change = { changed = "replan_future_milestones_from_review_draft", unchanged = "verify_milestone_review_edits", source_plan_violation = "failure", frozen_unit_violation = "failure", handler = { kind = "fixed-point", block = "replan_future_milestones_from_review_draft" } }
failure = "degrade"
degrade_default = "continue"
template_values = { REVIEW_SUBJECT = "whole milestone", ALTITUDE_CLAUSE = "Review altitude: milestone. The unit being reviewed is one fully built milestone. You may edit the working tree to make safe improvements. The remaining plan means the later, not-yet-built milestone briefs. Beyond correctness, judge the architecture: responsibilities and interfaces, dependencies, maintainability, and whether the structure will suffice for the remaining 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." }

Gate the review's edits, then commit them.

build
[[blocks.verify_milestone_review_edits]]
type = "gate"
if = "red"
then = "failure"

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

The reviewer's verdict reaches the run here, and a continue verdict records the milestone DONE.

build
[[blocks.build_one_milestone]]
type = "conditional"
condition = { kind = "channel-value", channel = "milestone_review", value = "halt" }
then = "failure"

[[blocks.build_one_milestone]]
type = "inner-unit"
op = "finish-entry"

replan_future_milestones_from_review_draft

The withheld draft is handed back to the same planner, scoped to the future briefs, then committed and gated.

build
[[blocks.replan_future_milestones_from_review_draft]]
type = "agent"
role = "plan"
prompt = "write-milestones"
inputs = ["$work-list.future"]
withheld_draft = "state/milestone-review-plan-draft.diff"
failure = "halt"
template_values = { ORCH = "$orchestration/milestones" }

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

[[blocks.replan_future_milestones_from_review_draft]]
type = "gate"
if = "red"
then = "failure"

The campaign ends when every milestone is DONE. An unbuilt milestone is never empty here — the branch above kept a zero-milestone plan out of this loop entirely.

build
[[rule_blocks.campaign_ending]]
condition = { kind = "work-list-empty" }
outcome = "success"

empty_campaign

The planner produced nothing to do. A first-class terminating disposition a parent (goal) branches on, rather than a stop-message substring. The reserved empty target reaches the same disposition, but a named block is what carries the message.

build
[[blocks.empty_campaign]]
type = "terminate"
disposition = "empty-campaign"
message = "the planner authored no milestone files — see the plan card / logs"