Composer
Composer is a Piyaz task orchestrator. Per iteration it picks the next ready task off the project's critical path, runs that task through a deterministic per-task workflow (research, plan, implement, CI gate, review, bounded fix loop), surfaces the verdict, merges when the user authorized it, propagates the result through the graph, and continues until a structural stop condition holds.
The orchestrator (this skill, running in the main loop) owns only the interactive seams: pick the task, resolve gates, run the merge gate, propagate. The token-heavy phase sequencing runs inside the workflow, off the orchestrator's context, dispatching the four phase agents in fresh windows with per-phase model and effort. This is the design's main token discipline: orchestration is JavaScript, not main-loop reasoning over a transcript that grows with every phase.
Composer is glue. The heavy lifting (task selection, refinement, the Completion Protocol, propagation) lives in the piyaz skill (skills/piyaz/SKILL.md); composer reuses those flows rather than duplicating them.
Invocation
/piyaz:composer: backlog mode. Pick the highest-value ready task each iteration; continue until a stop condition holds./piyaz:composer <taskRef>: single-task mode. Same pipeline applied to one task; exits after the iteration completes./piyaz:composer rework <taskRef|pr-url>: rework mode. HOTL requested changes on GitHub instead of merging; composer rounds that feedback back through the fix loop./piyaz:composer --pipelined: backlog mode with research-ahead (latency-only, costs tokens). Off by default; see Pipelined research-ahead.
No argument means backlog mode; rework plus an argument means rework mode; anything else is single-task.
Piyaz operating context
The canonical piyaz rules load with this skill. Downstream citations (conventions §1, artifacts §3, lifecycle §3) refer to this loaded text.
@skills/piyaz/references/conventions.md @skills/piyaz/references/artifacts.md @skills/piyaz/references/lifecycle.md @skills/piyaz/references/resilience.md
The per-task workflow
Each iteration's task runs through skills/composer/workflows/compose-task.js, launched with the Workflow tool:
Workflow({
scriptPath: "${CLAUDE_PLUGIN_ROOT}/skills/composer/workflows/compose-task.js",
args: { taskRef, taskId, projectId, categories, tagVocabulary,
pickEstimate, pickPriority, workType, tags, thinDescription,
mode, plannableOnly, resumeFrom, priorBrief, gateAnswers,
fixFindings, prUrl, priorFailure, estimate, flags },
})
If ${CLAUDE_PLUGIN_ROOT} does not resolve in the tool argument, substitute the absolute path of this plugin's root. The workflow runs in the background; the orchestrator is suspended until it returns, so it spends no context tokens while phases run.
The workflow dispatches the four phase agents by agentType, each with explicit model/effort/schema, the implementer with isolation:'worktree'. It runs research → plan → implement → ci-gate → review → [fix-loop ≤2 rotations], then returns one structured result. It does not merge, propagate, or touch edges; those are the orchestrator's seams. The phase contracts live in the agent files; do not duplicate them here.
| Phase | agentType | Writes to Piyaz | Workflow captures |
|---|---|---|---|
| 1. Research | piyaz:composer-researcher | refinement fields only (description, acceptanceCriteria, tags, category, priority, estimate, decisions); never status | brief, status, flags, confidence, refined estimate/work-type, proposed rewrites |
| 2. Plan | piyaz:composer-planner | implementationPlan, decisions; status='planned' on draft → planned only | status, section/step counts, open questions |
| 3. Implement | piyaz:composer-implementer | status='in_progress' (claim), status='in_review' (+ Completion Protocol); fix mode rotates in_review → in_progress → in_review | status, PR URL, AC counts, concerns |
| CI gate | generic (haiku) | nothing | green / red / pending / none, failing checks |
| 4. Review | piyaz:review (dispatched with a verdict schema) | nothing (read-only) | verdict, blocking findings |
The workflow result
The workflow returns exactly one of three shapes. Branch on result.status, not on prose:
status | Meaning | Orchestrator reaction |
|---|---|---|
DONE | Task ran to in_review (or planned for a plannable-only pick) | Surface the verdict, run the Merge gate, propagate |
NEEDS_DECISION | The research or plan phase gated; result.gate carries the trigger and result.phase names the raising phase | Resolve via Gates, then relaunch the workflow with the answer |
BLOCKED | A phase could not complete; result.phase and result.reason say which and why | Failure handling |
A DONE result also carries: outcome (in_review|planned), verdict, prUrl, ciState, acSatisfied/acTotal, rotations, escalated (true when a block verdict or an exhausted fix budget left findings unaddressed), blockingFindings, concerns. A null return (the workflow died on a terminal error) is treated as BLOCKED.
Session bootstrap
Once per session, before the first iteration:
- Resolve the project.
piyaz_project action='list'→action='select' projectId='...'. Single-task mode: alsopiyaz_query type='search' query='<taskRef>'to resolve the task UUID and current status. - Read meta.
piyaz_query type='meta'. Keep the categories and tag vocabulary for the workflow's research args; drop the status counts. - Stale-claim sweep. Scan the task list (
piyaz_query type='list') for tasks already atin_progress. Surface possible stale claims from dead sessions in the first pick rationale. - Set the merge policy. Ask once with
the AskUserQuestion tool:never(default; HOTL owns the merge),ask-each(confirm per PR), orauto-on-approve(merge automatically on anapproveverdict with green CI, and auto-remove safe worktrees at run end). Record the choice; it holds for the whole run. WhenAskUserQuestionis unavailable (headless), default tonever. - Init the run log.
mkdir -p .piyazand guard the gitignore (grep -qxF '.piyaz/' .gitignore 2>/dev/null || printf '\n.piyaz/\n' >> .gitignore). If.piyaz/composer-<projectIdentifier>.mdexists and ends withRUN_END, archive it to.piyaz/archive/composer-<projectIdentifier>-<date>.mdand start fresh; if it exists without aRUN_END, that is a resume signal — see Recovering after compaction first. When the unfinished log'sRUN_START mode=differs from this invocation, appendRUN_END reason=superseded-by-<mode>, archive, and start fresh. Then appendRUN_START mode=<...> mergePolicy=<...> project=<identifier>.
Then start iterating. There is nothing to install and nothing to confirm beyond the merge policy.
The loop
At the start of each iteration, materialize these todos and mark them off (the todo list is your compaction anchor): pick, launch workflow, handle result, surface verdict, merge gate, propagate.
digraph composer_iteration {
"Pick next task" [shape=box];
"Ready or plannable task?" [shape=diamond];
"STOP: backlog drained" [shape=doublecircle];
"Launch compose-task workflow" [shape=box];
"Result status?" [shape=diamond];
"Resolve gate with user" [shape=box];
"Continue this task?" [shape=diamond];
"STOP: iteration ends (single-task)" [shape=doublecircle];
"Failure handling" [shape=box];
"outcome = planned?" [shape=diamond];
"Surface verdict" [shape=box];
"Merge gate (per policy)" [shape=box];
"Propagate" [shape=box];
"Single-task mode?" [shape=diamond];
"STOP: iteration complete" [shape=doublecircle];
"Pick next task" -> "Ready or plannable task?";
"Ready or plannable task?" -> "STOP: backlog drained" [label="no"];