| name | roadmap |
| position | end |
| description | Plan the product's vertical slices into a build sequence — order the slices /shape produced across all domains, resolve their dependencies, and estimate each one's effort, writing only the plan onto each slice. The planning play in the ProductOS command model, after /shape. Reads the slices to judge order and effort but writes only the plan (order, effort, dependencies). Opens no delivery issue. |
| user-invocable | true |
roadmap
Take the vertical slices /shape produced and turn the pile into a plan. /roadmap reads
every slice across every shaped domain and lays out the build sequence: the order to
build them, the dependencies between them (within and across domains), and an effort
estimate for each. It writes that plan onto each slice's spine index entry — order, effort, resolved
depends_on, and the status flip to planned — and touches nothing else: the slice
records (composition) and every other part of the model stay byte-identical. One run
plans the whole set, because order only means something by comparison. Convention: a
lower order number means sooner.
Pipeline position: end. /roadmap CLOSES the strategy pipeline: after the ordering is persisted and verified, the D2 rule injects the close sequence commit-change → propose-change → review-change → merge-change, so the strategy change is committed, raised, reviewed, and merged. No start-change head — /vision opened the pipeline. It runs after /shape, since slices must exist before they can be planned. (#437)
Compiled From
This play was compiled from the roadmap ICE (reference/ice.md) by play-editor
(#466 Batch C; #467 Batch B — the checkpoint upgraded to a conditional learned gate,
see standards/rules/gate-config.md). Intent defines constraints (C1–C9) and failure
conditions (F1–F10); the expectation defines success scenarios (S1–S6), a Done means
(D1–D3, baked to stop-condition.yaml), and one recovery entry per failure condition.
To modify this play, update reference/ice.md and recompile with play-editor.
Do NOT edit this file manually — it is a compiled artifact.
Role
You are the orchestrator. You own the workflow and the step order. You delegate the
judgment — effort, resolving dependencies, and a value preference — to the
product-os-keeper agent via a JSON contract over files on disk, and you run every
mechanical part (the snapshot, the ordering math, the allowlisted persist, and the
post-apply checks) through bundled scripts. You never write slice YAML by hand, you never
assign the order numbers yourself, and you never persist before the single checkpoint
(C8) resolves — a typed approval, a recorded config skip, or a recorded policy
auto-pass.
Forbidden: hand-writing slice/plan YAML; assigning order numbers in prose instead of
via scripts/compute_plan.py; writing anything other than the plan fields
(order/effort/depends_on/status) on a slice's spine entry; editing a slice's composition
(its record); persisting by any route other than scripts/apply_roadmap.py; persisting
before Step 4 approval; touching the grounding docs, the node tree, the profile, the
lenses, decisions, or the _deferred bucket (C2).
Agent boundaries:
| Agent | Domain | Skill it invokes | Phases |
|---|
product-os-keeper | Read /shape's slices and judge the plan inputs — estimate effort per slice, resolve dependency_notes (+ shared functionalities + their spine depends_on) into concrete depends_on, propose a value order | author-roadmap | Draft |
product-os-keeper is the single domain agent this play uses (1 of the ≤5 budget).
No utility agents are needed — git/issue machinery is absent (position none). /roadmap
does not ground in the KB: planning is a judgment over the slices the model already
holds, not a placement question.
Pre-flight
| Check | Constraint | Action on Failure |
|---|
Resolve config + product_base (.garura/core/config.yaml) | — | Hard halt |
| The product model holds ≥1 slice to plan | C1 | Graceful exit |
Resolve config mechanically with the bundled resolver; /roadmap has no branch or issue
(position none):
python3 scripts/preflight.py --play roadmap --config .garura/core/config.yaml
python3 scripts/snapshot_model.py --product-base <product_base> --probe
preflight.py returns config facts (product_base, stm_base, evidence_record).
snapshot_model.py --probe reports slice_count without writing anything. If
slice_count is 0, exit gracefully: there is nothing to plan — run /shape first to
create slices. The probe is write-free, so re-entering pre-flight on a resume is safe;
the actual before-picture is captured at Step 1.
<working> is the run's STM working root, {stm_base}_shaping/roadmap/ — the baked
stop condition's relative paths resolve against it.
Right after the resolver, record the session identity stamp's start marker (#463 —
soft-fail, never a halt):
python3 scripts/session_stamp.py --phase start \
--marker "{stm_base}_shaping/roadmap/status/session-stamp-roadmap.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Resume check: if {stm_base}_shaping/roadmap/status.json exists, resume — skip
completed steps (Step 1's snapshot is therefore preserved), reset any in-progress step to
pending, continue from the first incomplete.
Task DAG
Create ALL tasks immediately after resolving config — before any domain work.
The play owns this DAG; the agent must not edit its top-level tasks.
[T1] Capture snapshot blockedBy: []
[T2] Draft plan blockedBy: [T1]
[T3] Compute plan blockedBy: [T2]
[T4] Checkpoint (approval) blockedBy: [T3]
[T5] Apply plan blockedBy: [T4]
[T6] Verify persisted blockedBy: [T5]
[TE1] commit-change (injected — end #1) blockedBy: [T6]
[TE2] propose-change (injected — end #2) blockedBy: [TE1]
[TE3] review-change (injected — end #3) blockedBy: [TE2]
[TE4] merge-change (injected — end #4) blockedBy: [TE3]
[T7] Scenario Validation blockedBy: [TE4]
[T8] Close blockedBy: [T7]
Mark each task in-progress before its step and completed right after its eval passes.
No runtime reordering. On resume, skip completed and reset in-progress to pending.
Workflow
Phase: Snapshot
Step 1 — Capture snapshot · Owner: play · Depends on: pre-flight
Take the pre-apply "before" picture — the spine and every slice across every shaped domain
(id, domain_ref, bundled functionalities, dependency_notes, current plan fields) and a
path→hash census of every file under product-os except _spine.yaml (the only file
/roadmap writes). Gated step, so a resume preserves it:
python3 scripts/snapshot_model.py --product-base <product_base> --out <working>/snapshot.json
SE (no eval here): Step 1 only records state; it is exercised by Step 3's plan and
proven by Step 6's non-destructive check.
Phase: Draft
Step 2 — Draft plan · Owner: product-os-keeper · Depends on: Step 1
The agent invokes author-roadmap to estimate each slice's effort, resolve its
dependencies into concrete depends_on slice ids, and propose a value order — drafting
plan-draft.yaml to STM:
{
"task": "for every slice in the snapshot, estimate effort, resolve depends_on (from dependency_notes + shared functionalities + their spine depends_on), and propose a value order; draft only",
"inputs": { "snapshot_path": "<working>/snapshot.json",
"product_base": "<product_base>" },
"outputs": { "plan_draft": "<working>/plan-draft.yaml" }
}
The skill reads the slices + their functionalities' grounding read-only and writes only the draft. It assigns no
order numbers and runs no topological sort — those are Step 3's deterministic job. It
returns the contract with the output path on disk — never inline content.
Phase: Compute
Step 3 — Compute plan · Owner: play · Depends on: Step 2
Turn the draft into a coherent, dependency-correct global plan:
python3 scripts/compute_plan.py --snapshot <working>/snapshot.json \
--plan-draft <working>/plan-draft.yaml --out <working>/plan.json
compute_plan.py topologically orders all slices so dependencies precede dependents
(value order breaks ties), assigns the distinct integer order 1..N across every domain,
carries each slice's effort, and detects cycles. It writes the machine coherence field
into the plan record (C9/#464): plan.json carries orders_coherent: true|false — this
field, not prose, is what the close's stop-condition gate (D3) reads.
SE-3 (F3/C3): within the computed plan, no slice precedes one it depends_on.
SE-4 (F4/C4): every slice in the plan has an integer order and a non-empty effort;
the orders are distinct and form 1..N.
SE-5 (F5/C5): the script exits 0 (no cycle) and the plan spans every shaped domain's
slices; on a non-zero exit (cycle or a slice missing from the draft) apply REC4/REC5 and
re-run before the checkpoint.
Phase: Checkpoint (conditional gate, C8)
Step 4 — Human review (class: standard, conditional) · Owner: play · Depends on: Step 3
This is the single checkpoint (C8). It is a conditional gate (#467) per
standards/rules/gate-config.md — /roadmap is one of the eleven conditional document
plays. Resolve it first match wins: pinned (n/a here) → gates.plays.roadmap → the
learned policy → gates.classes.standard → gates.default (absent ⇒ on). For the
policy lookup, classify the draft-vs-live change shape first (<working> is /roadmap's
draft root — it holds plan-draft.yaml and plan.json):
python3 scripts/classify_change.py --play roadmap --draft <working> \
--live <product_base> --out <working>/shape.json
Look the shape key up in the config-resolved policy (gates.conditional.policy):
auto-pass iff the shape is in the policy's auto: block AND not in never_auto:
AND Step 3 stands with no blocking finding (a compute_plan.py non-zero exit or a
cycle anomaly in plan.json — /roadmap's lint-equivalent). On auto-pass, do NOT wait:
record gate auto-passed by learned policy (shape: <shape-key>, policy v<version>) as
a Checkpoint Decisions row, include the draft's diff summary in the run record, append
the crossing's live-eval ledger line, and proceed to Step 5:
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play roadmap \
--issue <strategy issue> --shape <shape-key> --predicted auto --human auto_pass \
--policy-version <policy version> --ts <run ts>
Anything else resolves the gate on (an explicit gates.plays.roadmap: off instead
records gate skipped by config (<resolution path>) as a Checkpoint Decisions row and
proceeds on the computed plan). When on, present the plan inline — the slices in
order, each with its effort and its resolved dependencies, plus any cycle anomalies for
the human to break — render the approval prompt
(standards/templates/approval-prompt.md) and wait for the typed response. Approve →
continue to persist; cancel → halt with no plan written. Then append the crossing's
live-eval ledger line with the human's real action:
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play roadmap \
--issue <strategy issue> --shape <shape-key> --predicted gate \
--human <approved_clean|approved_edited|rejected> --ts <run ts>
<strategy issue> is the strategy-pipeline issue the run's branch carries (opened by
/vision's start-change). <gates.conditional.ledger> / <gates.conditional.policy>
resolve from config gates.conditional (defaults .garura/core/gate-evals.jsonl /
.garura/core/gate-policy.yaml); <policy version> is the policy file's version:
field. <run ts> is the run's own UTC timestamp, derived the same way the close
derives ts (date -u), passed by the orchestrator.
SE-8 (F8/C8): the plan is persisted only after this gate resolves — a typed
approval, a recorded config skip, or a recorded policy auto-pass — Step 5 is the sole
writer and depends on this step; no slice's plan fields change before Step 5.
SE-10 (F10): every crossing of this gate appended exactly one live-eval ledger line
(shape, predicted gate|auto, the human's real action or auto_pass), and an
auto-pass fired only for a shape the policy lists in auto: (and not in never_auto:)
with no blocking finding standing.
Phase: Apply
Step 5 — Apply plan · Owner: play · Depends on: Step 4
Persist on the fixed allowlist. apply_roadmap.py read-modify-writes the spine slices
index: it changes only order, effort, depends_on, and status on each planned
slice's spine entry, preserving every other part of the spine, and never opens the slice
records, the grounding docs, the node tree, the profile, the lenses, or decisions:
python3 scripts/apply_roadmap.py --plan <working>/plan.json \
--snapshot <working>/snapshot.json --product-base <product_base> \
--out-manifest <working>/apply-manifest.json
SE-1 (F1/C1): the apply manifest's changes are all on spine slice entries — no
non-slice entry received a plan.
SE-2 (F2/C2): each change touches only the plan fields (order/effort/depends_on/
status) on the spine — no slice record (composition), grounding doc, or other artifact appears.
Step 6 — Verify persisted · Owner: play · Depends on: Step 5
Prove the persisted model against the snapshot and the plan:
python3 scripts/validate_roadmap.py --snapshot <working>/snapshot.json \
--plan <working>/plan.json --apply-manifest <working>/apply-manifest.json \
--product-base <product_base>
SE-6 (F6/C6): non-destructive — every file under product-os except _spine.yaml is
byte-identical to the snapshot, and within the spine only the slices' plan fields changed
(domains, capabilities, functionalities, profile, epics, and each slice's non-plan fields
unchanged); nothing added or removed.
SE-7 (F7/C7): every slice spine entry the run wrote conforms to the spine schema —
order an integer, status planned, effort non-empty, depends_on resolving to real slices.
SE-9 (F9/C9): the Done means holds as machine state — plan-draft.yaml and plan.json
exist and plan.json reads orders_coherent: true — and the close's stop-condition
verdict reads held before the run may close COMPLETED; coherence asserted only in prose
fails this eval (REC9: re-run the draft and compute_plan.py, then re-evaluate).
(SE-1…SE-5 are re-asserted here against the persisted model.)
On any GAP, apply the matching recovery (REC1–REC7, REC9) and re-run.
Phase: End sequence (injected — D2 position: end)
The persisted ordering is a durable model change; the standard end sequence closes the
strategy pipeline (#437). Each member runs as a sub-play dispatched with parent_run_id
(emits only its own C1 evidence; this play's close absorbs it). Each member resolves its
own context from the branch and config; this play passes no hand-rolled git/PR/merge
logic.
Step E1 — commit-change · Owner: commit-change (sub-play) · Depends on: Step 6 —
commit the strategy-pipeline changes grouped by concern; no push.
Step E2 — propose-change · Owner: propose-change (sub-play) · Depends on: Step E1 —
self-review, push the branch, open the PR.
Step E3 — review-change · Owner: review-change (sub-play) · Depends on: Step E2 —
diff-scoped review, approve/reject verdict. A reject stops the sequence before merge.
Step E4 — merge-change · Owner: merge-change (sub-play) · Depends on: Step E3
(approve verdict) — merge the PR, switch to main and pull, delete the feature branch.
{
"play": "<commit-change | propose-change | review-change | merge-change>",
"parent_run_id": "<this run id>",
"inputs": {},
"outputs": { "result": "{stm_base}_roadmap/end/<member>.json" }
}
Phase: Scenario Validation
Step 7 — Scenario evals · Owner: play · Depends on: Step 6
- SCE-1 (S1 — planner, first plan): every non-deferred slice's spine entry has an
integer order and a non-empty effort; orders are distinct 1..N; every other part of the
spine and every other product-model file is byte-identical; the entries conform to the
spine schema.
- SCE-2 (S2 — architect, dependencies): for every resolved
A depends_on B,
order(B) < order(A); any cycle is in the checkpoint anomaly list and no order persisted
for it.
- SCE-3 (S3 — delivery lead, effort): every planned slice carries an effort estimate.
- SCE-4 (S4 — product manager, cross-domain): the orders form a single 1..N spanning
slices from every shaped domain, not a per-domain sequence.
- SCE-5 (S5 — product owner, re-run non-destructive): on an unchanged model every
product-model file is byte-identical; on a changed model only the plan fields differ on
any slice, and no non-slice file changed.
- SCE-6 (S6 — reviewer, the checkpoint): the checkpoint showed the ordered slices with
effort and dependencies and any anomalies, before any write — or, on the auto-pass path
(a policy-listed shape), the gate resolved with no wait and the recorded auto-pass, the
appended ledger line, and the diff summary stand in the approval's place.
Phase: Evidence & Close
Step 8 — Close · Owner: play · Depends on: Step 7
Run the Standard Play Close. /roadmap is a product-scoped play (no issue) — use the
product-scoped evidence base and slug. Evidence recording is play-only and config-gated
per the D1 evidence rule (standards/rules/evidence-recording.md).
evidence_template=$(cat "${ltm_project_target}standards/templates/evidence-file.md")
delivery_template=$(cat "${ltm_project_target}standards/templates/delivery-report.md")
ts=$(date -u +%Y%m%d-%H%M%S)
evidence_dest="${evidence_base}${ts}.md"
mkdir -p "$(dirname "$evidence_dest")"
session_stamp=$(python3 scripts/session_stamp.py --phase close \
--marker "${stm_base}_shaping/roadmap/status/session-stamp-roadmap.json")
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}_shaping/roadmap/" \
--out "${stm_base}_shaping/roadmap/status/stop-condition-roadmap.yaml"
sc_exit=$?
python3 scripts/distill_gate_policy.py --ledger "<gates.conditional.ledger>" --policy "<gates.conditional.policy>" --streak <gates.conditional.streak> --project "<project name from config>" || true
/roadmap is product-scoped: evidence_base="${product_base}_evidence/roadmap/" and
slug="${product_slug}" (the product, since one run plans the whole set).
Step C0 — bind the verdict. sc_exit == 0 (held) permits status: COMPLETED.
Anything else closes HALTED with exit_reason: stop_condition_unmet and the evidence's
Stop Condition section names every unmet clause. An unevaluable verdict is never a pass.
Step C1 — Write evidence file. Gated by the resolved evidence.record flag (global
- per-play
evidence.plays.roadmap; first match wins, absent ⇒ record). When false, skip
the write and record evidence skipped (record=false) in the report's pointer line.
Otherwise fill the evidence-file.md slots (play roadmap, run_id roadmap-${ts},
product_slug, started_at/completed_at, status per C0, exit_reason; artifacts produced: the
plan draft, the plan, the apply manifest, the slices planned, the stop-condition verdict;
step and scenario eval results SE-1…SE-10 / SCE-1…SCE-6; checkpoint decision from Step 4
including the order and any anomalies (incl. any gate skipped by config /
gate auto-passed by learned policy row) plus the gate ledger line(s) appended this run; the
session identity stamp fields from $session_stamp (#463): session_id, ledger_file,
ledger_start_offset, ledger_end_offset (null when unresolved — never blocks the close);
and stop_condition per C0 with the Stop Condition section filled) and write to
$evidence_dest. Do NOT hand-author the body.
Step C2 — Render delivery report. Also render the Next line: resolve this play in standards/rules/pipeline-next.md and emit **Next:** /<command> — <why>. Or run /next to see all recommended actions. (only /next pointer, or omit, when the mapped command is null), per play-close.md. Fill the delivery-report.md slots and output the
report: ## roadmap Delivered — ${product_slug}, the Run Summary table, the Pipeline
Steps table from the task DAG, the Artifacts Produced table (the ordered slices with
effort + dependencies, any anomalies), Next Steps (run /grill on the top-ordered slice's
functionalities to cut epics), and a pointer to $evidence_dest. Always emitted; never
gated.
Scenario Validation
| Scenario | Persona | Eval |
|---|
| S1 — first plan | planner | SCE-1 |
| S2 — dependencies | architect | SCE-2 |
| S3 — effort | delivery lead | SCE-3 |
| S4 — cross-domain | product manager | SCE-4 |
| S5 — re-run non-destructive | product owner | SCE-5 |
| S6 — the checkpoint | reviewer | SCE-6 |
Recovery
| For | Trigger | Direction | Handoff |
|---|
| F1 | a plan was written onto a non-slice artifact | revert it; /roadmap plans only /shape slices | autonomous |
| F2 | a write touched a slice's composition (record) or another part of the model | revert the out-of-scope write; /roadmap writes only order/effort/depends_on/status on the spine slices | autonomous |
| F3 | a slice is ordered ahead of one it depends_on | re-sort so dependencies precede dependents before persisting | autonomous |
| F4 | a planned slice lacks order or effort, or the orders aren't a coherent 1..N | recompute a complete, coherent plan — an integer order and an effort for every planned slice | autonomous |
| F5 | a dependency cycle, or a domain's slices left unplanned | surface the cycle at the checkpoint for a human to break, and include every shaped domain's slices in the single plan | human |
| F6 | a non-plan slice field changed, or a non-spine product-model file changed | restore the changed content and re-apply only the plan fields, after a human confirms the restore | human |
| F7 | a written slice spine entry fails the spine schema | re-emit the failing entry to conform before the play completes | autonomous |
| F8 | a plan was persisted before the checkpoint gate resolved (no typed approval, no recorded config skip, and no recorded policy auto-pass) | revert the premature write and re-present the checkpoint; persist only after the gate resolves | human |
| F9 | the run is about to close COMPLETED with the Done means unmet (a missing plan draft or computed plan record, or orders_coherent not true) | produce the missing artifact — re-run the draft and compute_plan.py so the plan record carries the machine order fields — then re-evaluate the stop condition; the close stays HALTED until the verdict reads held | autonomous |
| F10 | a conditional-gate crossing left no live-eval ledger line, or an auto-pass fired for a shape the policy does not list as auto (or that carried a blocking finding) | re-append the missing ledger line for the recorded crossing; when the auto-pass was unearned, re-run the gate as a live wait — render the approval prompt and wait for the typed response — before proceeding | autonomous |
Pause and Resume
Steps run top to bottom. On entry, resolve config, run the write-free pre-flight probe,
check the status marker ({stm_base}_shaping/roadmap/status.json), skip completed steps,
reset any in-progress step to pending, and continue. The pre-apply snapshot is captured
at Step 1 (a gated step) and preserved on resume, so the non-destructive comparison
always diffs against true pre-apply state. A fresh start with no marker runs everything
and creates the marker at Step 1.
Compilation Metadata
| Field | Value |
|---|
| fingerprint | sha256:e5fdd938574e720154ce938a20cc9eee1790aa7d6b761814f5c247e7046b4c91 (of reference/ice.md) |
| compiled_by | play-editor (#467 Batch B); prior: play-editor (#466 Batch C) |
| pipeline_position | end (commit-change → propose-change → review-change → merge-change tail; opened by /vision) |
| workflow_structure | A (single checkpoint, class standard, conditional gate per gate-config.md #467) |
| stop_condition | stop-condition.yaml (D1–D3), gate live at Step C0 |
| domain_agents | 1 (product-os-keeper) |
| utility_agents | 0 |
| skills_used | author-roadmap |
| scripts | 10 (preflight.py, snapshot_model.py, compute_plan.py — writes the machine orders_coherent field, apply_roadmap.py, validate_roadmap.py, classify_change.py — #467 shape classifier, gate_eval.py — #467 live-eval ledger, distill_gate_policy.py — #467 policy learner, check_stop_condition.py — Done-means gate, session_stamp.py — #463 identity stamp) |
| step_evals | 10 (SE-1…SE-10) |
| scenario_evals | 6 (SCE-1…SCE-6) |
| recovery_entries | 10 (one per failure condition; 7 autonomous / 3 human) |
Recompiled note (#467 Batch B): checkpoint upgraded to a conditional learned gate;
see gate-config.md.