| name | start-change |
| position | start |
| description | Open a unit of work cleanly — resolve or create the tracked issue, cut a feature branch off up-to-date main, set up a git worktree when config calls for it, and initialize the STM workspace. The start-of-pipeline play in the ProductOS command model. Use when beginning work on a change, starting an issue, or kicking off a feature. |
| user-invocable | true |
start-change
Open a unit of work cleanly: ensure a tracked issue, a feature branch cut from an
up-to-date main, a git worktree when config calls for it, and an initialized STM
workspace — so every play that runs after start-change begins from a known, identified
context.
Pipeline position: start. start-change is the start-of-pipeline primitive. The D2
pipeline-position rule injects it into any other play declared position: start. As the
primitive itself, start-change does not get a start composed into it.
Compiled From
This play was compiled from the start-change ICE (reference/ice.md) by play-editor
(#466 Batch A, Level 3 rollout per ADR 025; #467 Batch C gate-off recompile; #484 scripted
chain). Intent defines constraints (C1–C8) and failure conditions (F1–F8); the expectation
defines success scenarios (S1–S4), a Done means (D1–D4, 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. Issue work goes to a
utility agent (the fresh-issue match from a description is genuine judgment); the mechanical
branch work runs in a bundled script calling git directly, not an agent (C8, #484). You
never reason out git/gh inline in play prose.
Forbidden: running the mechanical branch work (cut/worktree/push) through an agent
dispatch (the #484 bug); ad-hoc gh/git in play prose; reimplementing issue logic inline.
Agent boundaries:
| Agent | Domain | Skill it invokes | Phases |
|---|
project-orchestrator | Issues: resolve an existing issue, create a new one (fresh-match judgment) | manage-issue | Execution |
project-orchestrator is a utility agent (issue plumbing + the fresh-issue match),
exempt from the domain-agent budget. The branch work is the bundled setup_branch.py — no
agent (C8). This play uses zero domain agents.
Pre-flight
| Check | Constraint | Action on Failure |
|---|
Resolve working base + config (.garura/core/config.yaml) | — | Hard halt |
| Git repository present | C2 | Hard halt |
Platform CLI available (gh) | C1 | Hard halt |
Read start-change.worktree flag (default false) | C3 | — |
| Resume marker check for this issue's start-change | C6 | Resume |
Resolve the deterministic pre-flight facts with the bundled resolver — not by inference. The
orchestrator captures the current branch and passes it in:
python3 scripts/preflight.py --play start-change \
--config .garura/core/config.yaml --branch "$(git branch --show-current)"
It returns the facts as JSON — stm_base, worktree (the start-change.worktree flag,
absent ⇒ false), branch, issue (null when starting fresh), on_default_branch,
evidence_record, ltm_project_target. The orchestrator holds these for every later path;
the table keeps the policy. The live checks (git repo present, gh available) stay as the
orchestrator's environment reads, not the resolver.
Resume check (C6, F5): if an issue is already known (passed in, or a status marker /
existing feature/<issue>-* branch is found), resume — switch to the existing branch,
reuse the existing STM, and do NOT create a second issue or branch. Skip completed steps,
reset any in-progress step to pending, continue from the first incomplete one.
Task DAG
Create ALL tasks immediately after resolving the working base — before any domain work.
The play owns this DAG; agents must not edit its top-level tasks.
[T1] Resolve Issue blockedBy: []
[T2] Confirm New Issue blockedBy: [T1]
[T3] Create / Finalize Issue blockedBy: [T2]
[T4] Set Up Branch + Worktree blockedBy: [T3]
[T5] Initialize STM Workspace blockedBy: [T4]
[T6] Scenario Validation blockedBy: [T5]
[T7] Close blockedBy: [T6]
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: Execution
Step 1 — Resolve Issue · Owner: project-orchestrator · Depends on: pre-flight
Dispatch a JSON contract; the agent invokes manage-issue (action: resolve) to find
an existing issue matching the work (or the issue number passed in), and writes the
result to disk. The contract moves only paths, never data:
{
"task": "resolve an existing tracked issue for this change (do not create)",
"inputs": { "description": "<working>/work-description.txt",
"issue_number": "<optional, if provided>" },
"outputs": { "issue": "<working>/issue.json" }
}
issue.json records { resolved: true|false, issue_number, title, created: false }.
Step 2 — Confirm New Issue · Owner: play · Depends on: Step 1
Skip when issue.json.resolved == true. When no issue was resolved, creating one is an
outward-facing action (class: standard). Resolve the gate switch per
standards/rules/gate-config.md FIRST:
- off (this project:
gates.plays.start-change: off) → do not wait; record
gate skipped by config (gates.plays.start-change) as a Checkpoint Decisions row in
evidence and proceed. A skip is always recorded, never silent.
- on → present the proposed new issue (title + summary) and wait for a typed
approval. Approve → continue to Step 3; cancel → halt.
Either way — the machine wall (C1/F7), never gated by the switch: before Step 3
creates anything, run the bundled validator on the proposal record:
python3 scripts/validate_issue.py \
--issue-json <working>/issue.json \
--description <working>/work-description.txt
It checks the title is present, ≥15 chars, and tagged ([ENH]/[BUG]/[FEAT]/[DOCS]/[TASK]
or a conventional prefix), and that the work description is complete. A non-zero exit is
a hard halt regardless of the gate state — REC7 hands to a human (fix the
title/description, re-run). This is the deterministic check that replaced the human eye
when the gate went off (#467).
Step 3 — Create / Finalize Issue · Owner: project-orchestrator · Depends on: Step 2
When Step 1 resolved an issue, carry it forward unchanged. When Step 2 approved a new
issue, the agent invokes manage-issue (action: create) and writes the finalized
issue.json (created: true, issue_number set).
SE-1 (F1/C1): issue.json has a non-empty issue_number (an issue is anchored
before any branch work); on resume, created == false and no new issue exists (F5).
SE-8 (F7/C1): validate_issue.py ran BEFORE the create (its verdict predates
created: true), and it exits 0 when re-run on the created issue's record — title
present/tagged, description complete. A non-zero exit at either point halted the run
to a human (REC7); no issue exists over a failed validation.
Step 4 — Set Up Branch + Worktree · Owner: play (script) · Depends on: Step 3
Run the bundled script — it pulls main to its latest origin tip, cuts
feature/<issue_number>-<slug> from it (or switches to it if it already exists), sets up a
git worktree iff start-change.worktree is true, pushes, and writes branch.json. No
agent (C8):
python3 scripts/setup_branch.py --issue <issue_number> \
--branch-name "feature/<issue_number>-<slug>" --base main \
$( [ "<start-change.worktree>" = "true" ] && echo --worktree ) \
--out "<working>/branch.json"
branch.json records { branch_name, base_sha, on_default_branch: false, worktree_path|null }.
SE-2 (F2/C2): current branch matches feature/<issue_number>-*, is not main, and
branch.json.base_sha equals the current origin/main tip.
SE-3 (F3/C3): git worktree list shows a worktree for the branch iff
start-change.worktree is true (present when on, absent when off).
SE-6 (C5): issue work went through the manage-issue skill (issue.json exists) and
the branch work through the bundled setup_branch.py (branch.json exists) — no inline
gh/git in play prose.
SE-9 (F8/C8): the branch cut/worktree/push ran as setup_branch.py, not an agent
dispatch — no repo-orchestrator/setup-branch was invoked for the branch work.
Step 5 — Initialize STM Workspace · Owner: play · Depends on: Step 4
Record the session identity stamp's start marker first (#463 — soft-fail, never a halt); the stamp start runs here rather than at pre-flight because pre-flight has no issue yet:
python3 scripts/session_stamp.py --phase start \
--marker "{stm_base}{issue}/status/session-stamp-start-change.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Run the mechanical script (it creates the five permitted structure keys idempotently):
python3 scripts/init_stm.py --stm-base <stm.base-path> --issue <issue_number>
Then commit the run artifacts on the fresh feature branch (C7) — play-owned mechanical
work, run as a command: the workspace opens with a clean tree and its own record
(issue.json, branch.json, work-description) aboard:
git add "{stm_base}{issue}/context/"
git commit -m "chore(stm): record start-change workspace context (#<issue>)"
SE-4 (F4/C4): <stm.base-path>/<issue_number>/ exists and contains the five
structure dirs (specs, evidence, checkpoint, context, review) before close.
Phase: Scenario Validation
Step 6 — Scenario Evals · Owner: play · Depends on: Step 5
- SCE-1 (S1 — developer, fresh start): a new issue, a
feature/<issue>-* branch off
the current origin/main tip, and the STM workspace all exist; the run artifacts are
committed on the branch; the stop-condition verdict reads held.
- SCE-2 (S2 — developer, existing issue): the provided issue was resolved (no new
issue created), its branch exists, and STM exists; the run artifacts are committed on
the branch; the stop-condition verdict reads held.
- SCE-3 (S3 — developer, resume): on a re-run, issue count and branch count are
unchanged and the session ends on the existing branch.
- SCE-4 (S4 — developer, worktree by config):
git worktree list shows the branch's
worktree when start-change.worktree is true, and shows none when it is false.
- SE-5 (F5/C6): a re-run for the same issue created neither a duplicate issue nor a
duplicate branch (counts unchanged vs. before the run).
Phase: Evidence & Close
Step 7 — Close · Owner: play · Depends on: Step 6
Run the Standard Play Close. It records evidence (when recording is on) and renders the
user-facing delivery report. Evidence recording is play-only and config-gated per the D1
evidence rule (standards/rules/evidence-recording.md).
SE-7 (F6/C7): the stop-condition verdict is held before the run closes COMPLETED;
the run artifacts are committed on the feature branch.
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}${issue}/status/session-stamp-start-change.json")
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}${issue}/" \
--out "${stm_base}${issue}/status/stop-condition-start-change.yaml"
sc_exit=$?
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.start-change; first match wins, absent ⇒ record).
When it resolves false, skip the write and record evidence skipped (record=false) in
the delivery report's pointer line. Otherwise fill the evidence-file.md slots (play
start-change, run_id start-change-${ts}, issue, started_at/completed_at, status,
artifacts produced: issue.json, branch.json, the STM workspace; step/scenario eval
results; checkpoint decisions from Step 2; commit reference N/A — no commit step; and
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
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 to the user: ## start-change Delivered — #${issue}, the Run Summary table, the
Pipeline Steps table built from the task DAG, the Artifacts Produced table, Next Steps
(the change is ready — run the build/implement play next), and a pointer to
$evidence_dest. Always emitted; never gated.
Scenario Validation
The success scenarios and their evals (run in Step 6):
| Scenario | Persona | Eval |
|---|
| S1 — fresh start | developer | SCE-1 |
| S2 — existing issue | developer | SCE-2 |
| S3 — resume | developer | SCE-3 |
| S4 — worktree by config | developer | SCE-4 |
Recovery
| For | Trigger | Direction | Handoff |
|---|
| F1 | no issue resolved or created before branch work | create the issue from the work description via manage-issue, then continue | autonomous |
| F2 | branch base is not the latest main, or current branch is main | pull main to latest and re-cut the branch from it, or move off main | autonomous |
| F3 | worktree state does not match start-change.worktree | reconcile to config — remove the stray worktree, or create the missing one | autonomous |
| F4 | close check finds the STM workspace missing | run scripts/init_stm.py to initialize it before reporting done | autonomous |
| F5 | a second run would create a duplicate issue or branch | detect the existing issue and branch and resume them instead of creating new ones | autonomous |
| F6 | the close would report COMPLETED without the Done means held | evaluate the stop condition and surface the unmet clauses; the run closes HALTED until state is fixed | autonomous |
| F7 | validate_issue.py exits non-zero on the proposed or created issue record | a human fixes the title (present, ≥15 chars, tagged) and/or completes the work description, then re-runs; the play never creates over a failed validation | human |
| F8 | the mechanical branch work ran through an agent instead of setup_branch.py | route the cut/worktree/push through setup_branch.py and remove the agent dispatch | autonomous |
Pause and Resume
Steps run top to bottom. On entry, resolve the issue (from input, status marker, or an
existing feature/<issue>-* branch), check the status marker, skip completed steps,
reset any in-progress step to pending, and continue. A fresh start with no marker runs
everything and creates the marker at Step 1. Resuming never duplicates the issue or
branch (C6, F5).
Compilation Metadata
| Field | Value |
|---|
| fingerprint | sha256:56a552759a176c2d7ed0e176c9822276d8031b36797a0d5617007c7831e495b9 (of reference/ice.md) |
| compiled_by | play-editor (#466 Batch A; #467 Batch C; #484 scripted chain) |
| pipeline_position | start |
| workflow_structure | B (fast execution flow) |
| stop_condition | stop-condition.yaml (D1–D4), gate live at Step C0 |
| domain_agents | 0 |
| utility_agents | 1 (project-orchestrator — issue work + fresh-match judgment) |
| skills_reused | manage-issue |
| scripts | 6 (init_stm.py, preflight.py, session_stamp.py, check_stop_condition.py, validate_issue.py, setup_branch.py) |
| step_evals | 9 (SE-1…SE-9) |
| scenario_evals | 4 (SCE-1…SCE-4) |
| recovery_entries | 8 (one per failure condition; 7 autonomous / 1 human) |
Recompile note (#484, scripted chain)
Intent change via reference/ice.md → recompile. Reworded C5 (issue work via
manage-issue; branch work is not a skill/agent) and added C8 (mechanical branch work runs
in the bundled setup_branch.py, never agent dispatch), F8, REC8, SE-9. Step 4 no longer
dispatches repo-orchestrator → setup-branch; it runs setup_branch.py (canonical copy
in play-creator/references/, stamped here). Issue work keeps project-orchestrator for
the fresh-issue match (genuine judgment). Utility agents 2 → 1; scripts 5 → 6. Guarantees
(issue-anchored, branch off latest main, worktree per config, resume-safe, Done-means
proven) unchanged. Fingerprint recomputed over the edited ICE.
Direct-edit deviation note (#434, pre-flight resolver)
Pre-flight resolution moved from orchestrator inference to the bundled scripts/preflight.py
(harness-led: config/branch/issue/worktree/changeset resolution is a script returning JSON
facts; the play keeps only the halt policy; the live git-repo and gh checks stay in the
table). The script is the canonical resolver stamped from
play-creator/references/preflight.py; a rebuild reproduces it (play-creator step 4).
Non-intent change — no constraint, failure, scenario, eval, or reference/ice.md touched, so
the fingerprint stands and no recompile is required. Direct edit; no recompile needed.
Compiled note (#467 Batch C)
The confirm-new-issue gate went off by config for this project
(gates.plays.start-change: off) in the same change that added its replacement machine
check: scripts/validate_issue.py (canonical copy:
play-creator/references/validate_issue.py) runs before any issue create, gate on or
off, and a non-zero exit hard-halts to a human (C1, F7, SE-8, REC7). The checkpoint
machinery is KEPT — the switch resolves per standards/rules/gate-config.md; flipping
the config back to on restores the wait; skips are always recorded in evidence. Owned
by the compiled play (ICE recompile, not a direct edit).
Direct-edit deviation note (#463, session identity stamp)
Non-intent change: the Standard Play Close gained the session identity stamp — scripts/session_stamp.py (canonical copy: play-creator/references/session_stamp.py) runs --phase start at pre-flight and --phase close in the close block; the evidence frontmatter carries session_id / ledger_file / ledger_start_offset / ledger_end_offset. Source of truth: standards/rules/play-close.md; play-creator emits the same lines so a rebuild converges. No constraint, failure, scenario, or eval changed; the fingerprint stands.