Files
ducoterra b88fbaec62 feat(phased-execution): push each phase commit after it is made
The harness now pushes the phase commit right after committing it
(PHASE_PUSH=1 default, new push_phase in lib.sh): upstream when set,
else 'git push -u <first remote> <branch>'; no remote = skip with a
notice. A push failure keeps the commit local, prints the same loud
ERROR contract as a commit failure, and stops the run — the next
phase's push sweeps the unpushed commit in. PHASE_PUSH=0 opts out with
a loud notice. SKILL.md (Commits + config table + exit codes) and the
executor prompts document the new behavior.
2026-09-10 07:56:30 -04:00

8.8 KiB

name, description
name description
phased-execution Runs the .agents/phases/ phased-execution pipeline (ported from opencode's next-phase/auto-phase commands). Use when the user asks to run the next task, run the next phase, run all phases, run the phase pipeline, or check pipeline status. Each task executes in a separate pi subprocess so this chat's context stays small.

Phased Execution

Phase state lives in files, not chat:

  • .agents/PLAN.md — master plan; LOCKED DECISIONS are binding
  • .agents/phases/todo/NN_name/ — a pending phase: 00_phase.md (objective, dependencies, task index, testing & quality, completion criteria) plus NN_task.md task files (task sort order = execution order)
  • .agents/phases/todo/NN_name.md — legacy single-file phase (still executable as one unit)
  • .agents/phases/complete/ — finished phases; mirrors the todo/ layout (completed task files and the phase overview move here)
  • .agents/reports/<phase>/<task>.a<N>.{md,err,validate} — per-task executor reports, stderr, and validation logs (legacy phases: .agents/reports/<phase>.a<N>.*)
  • .agents/validate.sh — the pass/fail gate, run after every task

The unit of execution is the task: each task runs in a separate pi process (fresh context) with bounded fixer retries. A task only moves to complete/ after the child exits 0, the child's stream ends with a clean final report, and .agents/validate.sh passes. When all of a phase's tasks are done, 00_phase.md runs as the phase's final pass (remaining inline work + completion criteria + phase-level verification); moving it completes the phase and is the PHASE_COMMIT commit point (one atomic commit — code + phase files + reports — pushed right after when the repo has a remote; children never commit; see Commits below). This chat only dispatches and relays results — do not implement task code yourself; that is what the subprocesses are for.

Live display

While a phase runs, scripts/progress.mjs relays the child's JSON stream to the terminal: tool calls, assistant text, ◐ thinking… / ◑ thought for Ns indicators, yellow ⧉ compacting context lines (these can take minutes — not a hang), and provider auto-retry notices. If the child dies mid-flush and the stream loses the final message, the report is recovered from the child's session file (.agents/phase-sessions/), so a completed phase is never lost to a truncated stream.

Commands

(Resolve scripts/ against this skill's directory.)

Run the next task, or a specific one:

bash scripts/run-task.sh                     # first pending task
bash scripts/run-task.sh 03_api/02_routes.md # specific task (warns if out of order)

Run one phase to completion — all of its remaining tasks, then its 00_phase.md final pass:

bash scripts/run-phase.sh          # next phase with pending work
bash scripts/run-phase.sh 03_api   # specific phase (warns if out of order)

Run the whole pipeline — every pending task, in order (phase order, then task order), stopping at the first task that fails after all retries:

bash scripts/auto-phase.sh

Re-running auto-phase.sh after a failure continues where it stopped.

After a run

Exit codes: 0 = success (or nothing to run), 1 = task failed after all attempts, or the phase commit/push failed, 130/143 = interrupted (Ctrl+C / SIGTERM). Failures always print a ✗ ERROR: line with the last error output — if the script's output looks like it ended abruptly, re-run it; the failed executor's session is resumed automatically (retries continue the child's own session, keeping its work).

Relay to the user: the task (or phase) name, its executor report (printed at the end of the script output), and the validation outcome. On failure, point the user at .agents/reports/<phase>/<task>.a*.{md,err,validate} (legacy phases: .agents/reports/<phase>.a*.*) — the script also prints a ready to run pi --session … -c “…” command to continue the failed session manually.

Commits

Children never commit — their prompts forbid git add/git commit (overriding any project “commit per phase” instruction), which is what keeps the commit deterministic instead of intermittent. By default (PHASE_COMMIT=1) the harness makes ONE atomic commit per completed phase at its commit point: the phase's code changes, the todo/→complete/ file move, and the executor reports, together. PHASE_COMMIT=0 opts out — the phase is then left uncommitted with a loud warning, never silently.

  • Scoped staging: a snapshot of the worktree taken at the phase's first unit (.agents/phase-sessions/dirty-<phase>, gitignored runtime storage) is subtracted, so the owner's pre-existing uncommitted work is left alone; pipeline runtime artifacts (phase-sessions/, pipeline.log) are never staged. The commit output prints exactly what went in.
  • Verified: the moved phase file (and the executor report, unless the project gitignores reports) is checked against the index before committing — the artifacts that used to go missing silently.
  • Loud on failure: a commit failure prints a ✗ ERROR: block with the git error and the exact hand-fix command, and stops the run. The phase stays complete (the work passed validation) — re-running will not re-execute it, and a later phase's commit will not sweep the miss in: fix the commit first, then re-run.
  • Subject: PHASE_COMMIT_SUBJECT (default phase: {{PHASE}}); the executor's final report becomes the commit body. --no-gpg-sign is always passed.
  • Pushed: with PHASE_PUSH=1 (default) the commit is pushed right after it is made — to the branch's upstream when one is set, otherwise git push -u <first remote> <branch>. A repo with no remote skips the push with a notice (the commit stays local). A push failure gets the same ✗ ERROR: contract as a commit failure: the commit is local and safe, the run stops, and the next phase's push sweeps the unpushed commit in once the push works. PHASE_PUSH=0 opts out — each phase commit is left local with a loud notice.

Configuration (environment variables)

Var Default Meaning
MAX_FIX_ATTEMPTS 3 Fixer retries per phase
PHASE_MODEL session default --model for child executors (e.g. anthropic/claude-sonnet-4-5)
PHASE_THINKING session default --thinking level for child executors
PHASE_COMMIT 1 1 (default) = the harness commits each completed phase as one atomic commit (code changes + the file move + executor reports) when its 00_phase.md final pass passes (legacy: when its file moves). 0 = opt out: the phase is left uncommitted with a loud warning. See Commits above
PHASE_COMMIT_SUBJECT phase: {{PHASE}} Commit subject template for PHASE_COMMIT=1 ({{PHASE}} = the phase directory name); the executor's final report becomes the commit body
PHASE_PUSH 1 1 (default) = push each phase commit right after it is made (upstream when set, else git push -u <first remote> <branch>); no remote = skip with a notice; a failure stops the run (the commit stays local — the next phase's push sweeps it in). 0 = leave commits local with a loud notice. See Commits above
PI_TRUST 0 1 = pass --approve (load project .pi/ settings/skills into children)
FRESH_FIX 0 1 = fixer retries start fresh instead of resuming the failed session
QUIET 0 1 = suppress live progress display (reports are still written)
PHASE_NOTIFY 1 0 = disable ntfy push notifications. When on and ~/.env/pi-ntfy.env exists, a notification is sent after every unit completes (task for each task file, phase for a phase's final pass)

Setup notes

  • First run creates .agents/validate.sh from assets/validate.sh if missing. It must be adapted to the project's real checks — it is the authoritative quality gate.
  • Phase directories are created by the phase-authoring skill or the /to-phase, /audit-create, /new-project, and /new-python-* prompt templates.
  • Legacy flat phase files (todo/NN_name.md) are still executed as a single unit; phase-authoring's migrate-phases-to-tasks.sh converts them to the directory layout (the phase's final pass then picks up any inline task list).
  • Child executor sessions are kept in .agents/phase-sessions/ (plus pipeline.log in .agents/); if the project is versioned, git-ignore those runtime artifacts only — .agents/ itself is tracked and committed. (The per-phase worktree snapshot dirty-<phase> is always kept there too.)
  • If you keep non-skill markdown (e.g. a README.md) in a skills directory (like ~/.pi/agent/skills/), pi warns “description is required” for it. Add a .gitignore in that directory listing the file — pi's skill scanner honors .gitignore, so the file is skipped.