| name | skill-orchestrate |
| description | Autonomous state machine that drives a task through its full lifecycle (research -> plan -> implement -> complete) without user confirmation between phases. Invoke for /orchestrate command. |
| allowed-tools | Agent, Bash, Read, Edit |
Orchestrate Skill
Fire-and-forget autonomous loop implementing the 10-state task lifecycle state machine.
Drives research, planning, implementation, and blocker escalation without user interaction.
Context References
Architecture documentation (load as needed):
.claude/docs/architecture/orchestrate-state-machine.md - Complete state table and transition diagram
.claude/docs/architecture/handoff-schema.md - Orchestrator handoff JSON schema
Infrastructure (source as needed):
.claude/scripts/skill-base.sh - Shared skill lifecycle functions
Execution Flow
Stage 0: Multi-Task Mode Detection
Parse multi_task_mode from the delegation context. If true, branch to multi-task stages (MT-1 through MT-5) in the Multi-Task Mode section below. If false or absent, fall through to Stage 1 (single-task mode).
Read from delegation context:
multi_task_mode (default: false)
session_id
focus_prompt (default: "")
lit_flag (default: "false")
If multi_task_mode is true: skip Stages 1-8 entirely and proceed to Stage MT-1.
Stage 1: Input Validation
Read from delegation context:
task_number (from task_context.task_number)
session_id, focus_prompt, lit_flag
continue_budget (default: false) → continue_budget_flag. Defect B: explicit,
operator-typed budget-continuation override for an exhausted work-cycle budget, threaded from
the command's --continue-budget flag. Never inferred from session_id, mtime, or any
automatic signal. See Stage 2 below for the override mechanism.
Resolve from specs/state.json:
PADDED_NUM=$(printf "%03d" "$task_number")
TASK_DATA=$(jq -r --argjson num "$task_number" \
'.active_projects[] | select(.project_number == $num)' \
specs/state.json)
If TASK_DATA is empty: exit with error "Task $task_number not found in state.json".
Extract: PROJECT_NAME, TASK_TYPE (default: "general"), DESCRIPTION, TASK_DIR="specs/${PADDED_NUM}_${PROJECT_NAME}".
Then resolve the absolute anchor that every dispatched agent will be handed. TASK_DIR stays
relative (many consumers below depend on that); TASK_DIR_ABS is the anchor that goes into
delegation contexts, because a dispatched agent has no reliable way to know what the ambient
working directory will be when its Write tool runs.
TASK_DIR_ABS="${TASK_DIR_ABS:-${SKILL_REPO_ROOT:-$(pwd)}/${TASK_DIR}}"
HANDOFF_PATH_ABS="${TASK_DIR_ABS}/.orchestrator-handoff.json"
Stage 1b: Resolve Task-Type Routing
Map task_type to the correct research, plan, and implementation agents via the single canonical
agent resolver, command-route-agent.sh — sourced from the shared manifest-routing-lib.sh
ladder (the same one command-route-skill.sh uses), against each manifest's routing_agents
declarations. No case table, no directory probe, no sed derivation: agent names are declared
data, not derived strings (see context/guides/manifest-routing-schema.md).
source .claude/scripts/command-route-agent.sh "research" "$TASK_TYPE" "general-research-agent" ""
RESEARCH_AGENT="$AGENT_NAME"
source .claude/scripts/command-route-agent.sh "plan" "$TASK_TYPE" "planner-agent" ""
PLANNER_AGENT="$AGENT_NAME"
source .claude/scripts/command-route-agent.sh "implement" "$TASK_TYPE" "general-implementation-agent" ""
IMPLEMENT_AGENT="$AGENT_NAME"
echo "[orchestrate] Task type: $TASK_TYPE → research=$RESEARCH_AGENT, plan=$PLANNER_AGENT, implement=$IMPLEMENT_AGENT"
Stage 2: Loop Guard Initialization
Create or read the loop guard file. This tracks cycle count across conversational turns.
Ephemeral, never committed. .orchestrator-loop-guard is per-cycle runtime state with no
freshness check on read (see the resume branch below: any syntactically valid guard file at this
path is trusted, with no session_id or mtime comparison against the current dispatch). A
git-restored copy of a stale guard would silently resume a wrong cycle_count/infra_failures
pair — exactly the hazard this file's gitignore coverage exists to prevent. See
context/standards/orchestrator-runtime-files.md for the full two-class policy and rationale.
MAX_CYCLES=5
loop_guard_init_json=$(bash .claude/scripts/orchestrate-loop-guard-init.sh "$TASK_DIR" "${HANDOFF_PATH_ABS}")
loop_guard_file=$(echo "$loop_guard_init_json" | jq -r '.loop_guard_file')
handoff_file=$(echo "$loop_guard_init_json" | jq -r '.handoff_file')
MAX_INFRA_FAILURES=$(echo "$loop_guard_init_json" | jq -r '.max_infra_failures')
if [ -f "$loop_guard_file" ] && jq empty "$loop_guard_file" 2>/dev/null; then
peek_cycle_count=$(jq -r '.cycle_count // 0' "$loop_guard_file")
if [ "$peek_cycle_count" -ge "$MAX_CYCLES" ]; then
if [ "$continue_budget_flag" = "true" ]; then
exhaust_ts=$(date -u +%s)
exhausted_guard_dest="${TASK_DIR}/.exhausted-loop-guard-${exhaust_ts}.json"
echo "[orchestrate] BUDGET EXHAUSTED (cycle_count=${peek_cycle_count}/${MAX_CYCLES}) — --continue-budget authorized a fresh budget. Archiving exhausted guard to ${exhausted_guard_dest} for auditability, reinitializing at cycle_count=0 with cross-invocation history fields (dispatch_seq_counter, detected_defects) preserved." >&2
if cp "$loop_guard_file" "$exhausted_guard_dest" 2>/dev/null; then
jq --arg updated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '.cycle_count = 0 | .last_updated = $updated' \
"$exhausted_guard_dest" > "${loop_guard_file}.tmp" && mv "${loop_guard_file}.tmp" "$loop_guard_file"
else
echo "[orchestrate] WARNING: could not archive exhausted guard to ${exhausted_guard_dest}; proceeding without archiving (cycle_count reset in place)." >&2
jq --arg updated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '.cycle_count = 0 | .last_updated = $updated' \
"$loop_guard_file" > "${loop_guard_file}.tmp" && mv "${loop_guard_file}.tmp" "$loop_guard_file"
fi
else
echo "[orchestrate] ERROR: work-cycle budget exhausted (cycle_count=${peek_cycle_count}/${MAX_CYCLES}). This is a budget limit, not an error condition -- the task's plan may still have incomplete phases." >&2
echo "[orchestrate] To continue this task's work, explicitly authorize a fresh budget: /orchestrate ${task_number} --continue-budget" >&2
exit 1
fi
fi
fi
if [ -f "$loop_guard_file" ] && jq empty "$loop_guard_file" 2>/dev/null; then
cycle_count=$(jq -r '.cycle_count // 0' "$loop_guard_file")
infra_failures=$(jq -r '.infra_failures // 0' "$loop_guard_file")
detected_defects=$(jq -c '.detected_defects // []' "$loop_guard_file")
dispatch_seq_counter=$(jq -r '.dispatch_seq_counter // 0' "$loop_guard_file")
guard_session_id=$(jq -r '.session_id // ""' "$loop_guard_file")
if [ -n "$guard_session_id" ] && [ "$guard_session_id" != "$session_id" ]; then
echo "[orchestrate] INFO: loop guard was last written by a different session_id ('${guard_session_id}' vs current '${session_id}') — expected on conversational resume, not gated."
fi
echo "[orchestrate] Resuming — cycle $cycle_count of $MAX_CYCLES (infra failures: $infra_failures of $MAX_INFRA_FAILURES)"
else
if jq -n \
--arg session_id "$session_id" \
--argjson max_cycles "$MAX_CYCLES" \
--argjson max_infra_failures "$MAX_INFRA_FAILURES" \
--arg started "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{
"session_id": $session_id,
"cycle_count": 0,
"max_cycles": $max_cycles,
"infra_failures": 0,
"max_infra_failures": $max_infra_failures,
"current_state": "reading",
"detected_defects": [],
"started": $started,
"last_updated": $started,
"dispatch_seq_counter": 0
}' | bash .claude/scripts/task-lock.sh init-marker "$loop_guard_file"; then
cycle_count=0
infra_failures=0
detected_defects='[]'
dispatch_seq_counter=0
echo "[orchestrate] Starting fresh — MAX_CYCLES=$MAX_CYCLES, MAX_INFRA_FAILURES=$MAX_INFRA_FAILURES"
else
cycle_count=$(jq -r '.cycle_count // 0' "$loop_guard_file")
infra_failures=$(jq -r '.infra_failures // 0' "$loop_guard_file")
detected_defects=$(jq -c '.detected_defects // []' "$loop_guard_file")
dispatch_seq_counter=$(jq -r '.dispatch_seq_counter // 0' "$loop_guard_file")
echo "[orchestrate] Resuming (lost init race) — cycle $cycle_count of $MAX_CYCLES (infra failures: $infra_failures of $MAX_INFRA_FAILURES)"
fi
fi
source .claude/scripts/skill-base.sh
mint_dispatch_seq() {
skill_orchestrate_mint_dispatch_seq "$loop_guard_file"
}
blocker_escalation_count=$(echo "$loop_guard_init_json" | jq -r '.blocker_escalation_count')
MAX_BLOCKER_ESCALATIONS=$(echo "$loop_guard_init_json" | jq -r '.max_blocker_escalations')
drift_inspection_count=0
MAX_DRIFT_INSPECTIONS=1
DRIFT_COMPLETION_THRESHOLD=0.70