- 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
- Task, 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):
- `.opencode/docs/architecture/orchestrate-state-machine.md` - Complete state table and transition diagram
- `.opencode/docs/architecture/handoff-schema.md` - Orchestrator handoff JSON schema
- `.opencode/docs/architecture/dispatch-agent-spec.md` - Fork vs. named subagent dispatch spec
Infrastructure (source as needed):
- `.opencode/scripts/skill-base.sh` - Shared skill lifecycle functions
- `.opencode/scripts/dispatch-agent.sh` - Fork vs. named subagent dispatch 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 existing Stage 1 (single-task mode unchanged).
```bash
source .opencode/scripts/skill-base.sh
multi_task_mode=$(echo "$delegation_context" | jq -r '.multi_task_mode // false')
session_id=$(echo "$delegation_context" | jq -r '.session_id')
focus_prompt=$(echo "$delegation_context" | jq -r '.focus_prompt // ""')
if [ "$multi_task_mode" = "true" ]; then
echo "[orchestrate] Multi-task mode detected — branching to MT-1"
# Continue to Stage MT-1 (see Multi-Task Mode section)
# All single-task stages (1-8) are SKIPPED in multi-task mode
goto_multi_task=true
else
goto_multi_task=false
fi
# If goto_multi_task=true, skip Stages 1-8 and proceed to Stage MT-1
```
---
### Stage 1: Input Validation
```bash
source .opencode/scripts/skill-base.sh
task_number=$(echo "$delegation_context" | jq -r '.task_context.task_number')
session_id=$(echo "$delegation_context" | jq -r '.session_id')
focus_prompt=$(echo "$delegation_context" | jq -r '.focus_prompt // ""')
# Read task state without blocking on terminal states (orchestrate handles them gracefully)
PADDED_NUM=$(printf "%03d" "$task_number")
TASK_DATA=$(jq -r --argjson num "$task_number" \
'.active_projects[] | select(.project_number == $num)' \
specs/state.json)
if [ -z "$TASK_DATA" ]; then
echo "ERROR: Task $task_number not found in state.json" >&2
exit 1
fi
PROJECT_NAME=$(echo "$TASK_DATA" | jq -r '.project_name')
TASK_TYPE=$(echo "$TASK_DATA" | jq -r '.task_type // "general"')
DESCRIPTION=$(echo "$TASK_DATA" | jq -r '.description // ""')
TASK_DIR="specs/${PADDED_NUM}_${PROJECT_NAME}"
```
### Stage 1b: Resolve Task-Type Routing
Map task_type to the correct research and implementation agents using extension manifests.
```bash
# Resolve agents by task_type — consult extension manifests for non-core types
case "$TASK_TYPE" in
lean4|lean)
RESEARCH_AGENT="lean-research-agent"
IMPLEMENT_AGENT="lean-implementation-agent"
;;
neovim)
RESEARCH_AGENT="neovim-research-agent"
IMPLEMENT_AGENT="neovim-implementation-agent"
;;
nix)
RESEARCH_AGENT="nix-research-agent"
IMPLEMENT_AGENT="nix-implementation-agent"
;;
*)
RESEARCH_AGENT="general-research-agent"
IMPLEMENT_AGENT="general-implementation-agent"
;;
esac
echo "[orchestrate] Task type: $TASK_TYPE → research=$RESEARCH_AGENT, implement=$IMPLEMENT_AGENT"
```
**Extension resolution**: If a task_type is not in the case table above, check for an extension manifest:
```bash
manifest=".opencode/extensions/${TASK_TYPE}/manifest.json"
if [ -f "$manifest" ]; then
ext_research=$(jq -r ".routing.research[\"$TASK_TYPE\"] // empty" "$manifest")
ext_implement=$(jq -r ".routing.implement[\"$TASK_TYPE\"] // empty" "$manifest")
# Map skill names to agent names (skill-X-Y -> X-Y-agent)
if [ -n "$ext_research" ]; then
RESEARCH_AGENT=$(echo "$ext_research" | sed 's/^skill-//' | sed 's/$/-agent/')
fi
if [ -n "$ext_implement" ]; then
IMPLEMENT_AGENT=$(echo "$ext_implement" | sed 's/^skill-//' | sed 's/$/-agent/')
fi
fi
```
### Stage 2: Preflight — Loop Guard
Create or read the loop guard file. This tracks cycle count across conversational turns.
```bash
MAX_CYCLES=5
loop_guard_file="${TASK_DIR}/.orchestrator-loop-guard"
handoff_file="${TASK_DIR}/.orchestrator-handoff.json"
mkdir -p "$TASK_DIR"
if [ -f "$loop_guard_file" ] && jq empty "$loop_guard_file" 2>/dev/null; then
# Resume: read existing guard
cycle_count=$(jq -r '.cycle_count // 0' "$loop_guard_file")
echo "[orchestrate] Resuming — cycle $cycle_count of $MAX_CYCLES"
else
# Fresh start: create guard
cycle_count=0
jq -n \
--arg session_id "$session_id" \
--argjson max_cycles "$MAX_CYCLES" \
--arg started "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{
"session_id": $session_id,
"cycle_count": 0,
"max_cycles": $max_cycles,
"current_state": "reading",
"started": $started,
"last_updated": $started
}' > "$loop_guard_file"
echo "[orchestrate] Starting fresh — MAX_CYCLES=$MAX_CYCLES"
fi
# Blocker escalation counter (reset each /orchestrate invocation)
blocker_escalation_count=0
MAX_BLOCKER_ESCALATIONS=2
# Drift detection constants (reset each /orchestrate invocation)
drift_inspection_count=0
MAX_DRIFT_INSPECTIONS=1
DRIFT_COMPLETION_THRESHOLD=0.70
DRIFT_REVISION_THRESHOLD=0.30
```
### Stage 3: State Machine Loop
The loop runs until a terminal condition is reached or MAX_CYCLES is hit.
```
while [ "$cycle_count" -lt "$MAX_CYCLES" ]; do
```
At the top of each iteration:
**3a. Read current task status**
```bash
current_status=$(jq -r --argjson num "$task_number" \
'.active_projects[] | select(.project_number == $num) | .status' \
specs/state.json)
echo "[orchestrate] Cycle $((cycle_count + 1))/$MAX_CYCLES — status: $current_status"
```
**3b. Update loop guard with current state**
```bash
jq --arg state "$current_status" \
--arg updated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--argjson count "$cycle_count" \
'.current_state = $state | .last_updated = $updated | .cycle_count = $count' \
"$loop_guard_file" > "${loop_guard_file}.tmp" && mv "${loop_guard_file}.tmp" "$loop_guard_file"
```
**3c. Dispatch by state** (see State Handlers in Stage 4)
---
### Stage 4: State Handlers
#### State: `not_started` or `not started`
Dispatch research via named subagent (resolved by task type in Stage 1b).
```bash
# Preflight: mark task as RESEARCHING before dispatch
skill_preflight_update "$task_number" "research" "$session_id"
```
```
dispatch_instructions = dispatch_agent "$RESEARCH_AGENT" \
"Research task $task_number: $DESCRIPTION${focus_prompt:+. User focus: $focus_prompt}" \
'{"task_number": N, "task_type": "T", "session_id": "S", "orchestrator_mode": false}' \
"false"
```
Invoke the Task tool per dispatch_instructions (subagent_type: $RESEARCH_AGENT).
After Task tool returns: read handoff (Stage 5). Increment cycle_count.
#### State: `researching`
In-flight state (another session is actively researching). Exit with warning.
```
echo "[orchestrate] WARNING: Task $task_number is currently being researched in another session."
echo "Wait for the research to complete, then run /orchestrate $task_number again."
EXIT (partial)
```
#### State: `researched`
Dispatch planning via named subagent.
```bash
# Preflight: mark task as PLANNING before dispatch
skill_preflight_update "$task_number" "plan" "$session_id"
```
```
research_artifacts=$(jq -c '[.active_projects[] | select(.project_number == N) | .artifacts // [] | .[] | select(.type == "report")] | .[0].path // ""' specs/state.json)
dispatch_instructions = dispatch_agent "planner-agent" \
"Create implementation plan for task $task_number${focus_prompt:+. User focus: $focus_prompt}" \
'{"task_number": N, "task_type": "T", "session_id": "S", "research_artifacts": [...], "orchestrator_mode": false}' \
"false"
```
Invoke the Task tool per dispatch_instructions (subagent_type: planner-agent).
After Task tool returns: read handoff. Increment cycle_count.
#### State: `planning`
In-flight state. Exit with warning (same pattern as `researching`).
#### State: `planned` or `implementing`
Dispatch implement via named subagent with `orchestrator_mode: true` (resolved by task type in Stage 1b).
```bash
plan_path=$(ls -1 "${TASK_DIR}/plans/"*.md 2>/dev/null | sort -V | tail -1)
```
```bash
# Preflight: mark task as IMPLEMENTING before dispatch
skill_preflight_update "$task_number" "implement" "$session_id"
```
```
dispatch_instructions = dispatch_agent "$IMPLEMENT_AGENT" \
"Implement task $task_number following the plan${focus_prompt:+. User focus: $focus_prompt}" \
'{"task_number": N, "task_type": "T", "session_id": "S", "orchestrator_mode": true,
"plan_path": "$plan_path"}' \
"false"
```
Invoke the Task tool per dispatch_instructions (subagent_type: $IMPLEMENT_AGENT).
After Task tool returns: read handoff. Increment cycle_count.
#### State: `partial`
Read `.orchestrator-handoff.json` to determine sub-state:
```bash
handoff=$(cat "$handoff_file" 2>/dev/null || echo '{}')
blockers=$(echo "$handoff" | jq -c '.blockers // []')
continuation=$(echo "$handoff" | jq -c '.continuation_context // null')
blocker_count=$(echo "$blockers" | jq 'length')
```
**Sub-state: continuation available** (continuation != null AND has handoff_path):
Dispatch implement with continuation context (resolved by task type in Stage 1b).
```bash
# Preflight: mark task as IMPLEMENTING before resume dispatch
skill_preflight_update "$task_number" "implement" "$session_id"
```
```
dispatch_instructions = dispatch_agent "$IMPLEMENT_AGENT" \
"Resume implementation for task $task_number from continuation handoff${focus_prompt:+. User focus: $focus_prompt}" \
'{"task_number": N, ..., "orchestrator_mode": true,
"plan_path": "$plan_path",
"continuation_context": {continuation_context_object}}' \
"false"
```
Ver en GitHub