Files

9.0 KiB

name, description, user_invocable, allowed-tools
name description user_invocable allowed-tools
orchestrate Check agent task state and launch container agents for ready tasks. Designed for use with /loop (e.g., /loop 2m /orchestrate) to auto-dispatch work to container agents. true Read, Write, Edit, Bash(date *), Bash(cat *), Bash(hostname *), Bash(docker *), Bash(jq *), Bash(git *)

/orchestrate Skill

orchestrate

You are the task orchestrator. Check task state, update statuses, and launch container agents for tasks that are ready to run. Each task runs in its own git worktree for isolation.

Pre-gathered context

Current date

!date +%Y-%m-%dT%H:%M:%S

Hostname

!hostname

Task state

!cat .agent-tasks.json 2>/dev/null || echo "NO_TASK_FILE"

Instructions

If the task state file doesn't exist, say "No .agent-tasks.json found. Run /decompose first." and stop.

Step 0: Dispatch pre-flight (first invocation only)

On the very first invocation of a dispatch session, run the pre-flight checklist from ~/dev/claude/claude/agent-dispatch-preflight.md before proceeding. All four checks must pass:

  1. CP reachable — confirm the health endpoint returns 200 (see claude/agent-runtimes-cp.md for CP_URL).
  2. A dispatcher is polling — verify at least one dispatcher is active at the CP dispatchers endpoint.
  3. Scaffolding present — .agent-tasks.json passes jq empty, referenced templates and repos are resolvable, and agent push keys are loaded.
  4. Auth done this session — CLAUDE_CODE_OAUTH_TOKEN is set or readable from secrets.

If any check fails, report which check failed and wait for the user to remediate. Do not proceed to Step 1 until all four pass.

"First invocation" means the first time this skill runs in a loop session. On subsequent loop invocations, skip Step 0.

Step 1: Check running containers

For each task with status: "running", check if the container is still alive:

docker inspect --format '{{.State.Status}}' <container_id> 2>/dev/null
  • If container status is exited: get exit code with docker inspect --format '{{.State.ExitCode}}'
    • Exit code 0 → set task status to completed, record completed_at and exit_code
    • Exit code non-zero → set task status to failed, record exit_code and capture last 20 lines of logs with docker logs --tail 20 <container_id>, store in error
  • If container not found (removed or wrong host): mark as failed with error "Container not found"
  • If container still running: leave as running

On task completion (success or failure):

  • Do NOT remove the worktree yet — the user may want to review changes on the branch
  • For successful tasks, note that branch contains the changes

Step 2: Identify ready tasks

A task is ready when:

  • Its status is pending
  • ALL tasks in its depends_on list have status completed

If a dependency has status failed, mark the dependent task as blocked (it cannot proceed).

Step 3: Launch ready tasks

Respect max_concurrent from .agent-tasks.json. Count tasks with status: "running" — if at the limit, skip launching and wait for the next cycle.

For each ready task:

3a. Create a git worktree

Determine the branch point:

  • If the task has no dependencies, branch from the current HEAD: git worktree add .worktrees/<task-id> -b agent/<task-id>
  • If the task depends on one completed task, branch from that task's branch: git worktree add .worktrees/<task-id> -b agent/<task-id> agent/<dep-task-id>
  • If the task depends on multiple completed tasks, create a merge base first:
    git branch agent/<task-id>-base
    git checkout agent/<task-id>-base
    git merge --no-edit agent/<dep-1> agent/<dep-2> ...
    git checkout -  # back to original branch
    git worktree add .worktrees/<task-id> -b agent/<task-id> agent/<task-id>-base
    

Record the branch and worktree path in the task state.

3b. Copy dependency outputs into the worktree

For tasks with dependencies, the dependency's output files (listed in writes) are likely untracked in git — they exist only in the dependency's worktree. Copy them into the new worktree:

# For each completed dependency:
for dep_task_id in <depends_on>; do
  # Copy the files that dep_task listed in its "writes" field
  for file in <dep_task.writes>; do
    cp .worktrees/<dep_task_id>/$file .worktrees/<task-id>/$file
  done
done

Also copy any untracked files from the main tree that the task needs (listed in reads):

  • Spec files, plan files, etc. that may not be committed
  • Check with git status whether files exist in the worktree; copy from main tree if missing

This step is critical — worktrees only contain committed content. Without it, agents cannot read dependency outputs or untracked project files.

3c. Launch the container

Determine the model to use:

  • Read the task's model field from .agent-tasks.json
  • If model is set, use that value (e.g., claude-opus-4-20250514, claude-sonnet-4-20250514)
  • If model is not set or null, default to claude-sonnet-4-20250514
docker run -d \
  -e CLAUDE_CODE_OAUTH_TOKEN="<token>" \
  -e ENFORCE_SUBSCRIPTION_PRICING=true \
  -v <absolute_worktree_path>:/project \
  -w /project \
  --entrypoint uid-wrapper.sh \
  agent-claude:latest \
  claude --print --dangerously-skip-permissions --model <model> "<task prompt>"

Important:

  • Use -d (detached) so the container runs in the background — do NOT use --rm so we can inspect logs after exit
  • Mount the worktree path (not the main project) as /project
  • Capture the container ID from docker run output
  • The CLAUDE_CODE_OAUTH_TOKEN must be available in the current environment. If not set, read it from ~/dev/claude/secrets/claude/long_lived_oauth_token (extract the value after value: )
  • If the task's reads list references paths outside the project (e.g., /foundations for best practices), mount those as additional read-only volumes
  • Container agents do NOT have web search capability. Do not include "use web search" in task prompts unless web search support has been explicitly configured for the container.

After launching, update the task in .agent-tasks.json:

  • Set status to running
  • Set host to the current hostname
  • Set container_id to the docker container ID (full ID, not short)
  • Set started_at to current ISO timestamp
  • Set branch and worktree to the values from step 3a

Step 4: Report status

Print a concise status summary:

Agent Tasks — <project name>
─────────────────────────────
  completed  task-a: Write payload spec ✓ (branch: agent/task-a)
  completed  task-b: Write entrypoint spec ✓ (branch: agent/task-b)
  running    task-c: Write harness spec (container abc123..., 3m elapsed)
  pending    task-d: Implement resolver (waiting on: task-c)
  blocked    task-e: Integration tests (blocked by failed: task-f)
  failed     task-f: Build images (exit code 1)
─────────────────────────────
3/6 complete | 1 running | 1 pending | 1 blocked
Concurrency: 1/3 slots used

Step 5: Update task state file

Write the updated .agent-tasks.json with all status changes.

Step 6: Merge completed branches (when all done)

When ALL tasks are resolved (no pending or running remaining):

  1. Print "All tasks resolved."
  2. List completed task branches with a summary of what each contains
  3. Suggest the user review and merge: git merge agent/<task-id> for each, or git merge agent/<task-1> agent/<task-2> ... for an octopus merge
  4. Note that worktrees can be cleaned up with: git worktree remove .worktrees/<task-id>
  5. Suggest cancelling the loop if running via /loop

Do NOT auto-merge — the user should review and decide.

Automation Notes

  • This skill is designed to be called repeatedly via /loop 2m /orchestrate
  • Each invocation is stateless — it reads .agent-tasks.json, checks containers, updates, and exits
  • Keep output concise when called in a loop — just the status table unless something changed
  • On first run, if CLAUDE_CODE_OAUTH_TOKEN is not in the environment, read it once and export it for subsequent runs

Stall circuit-breaker: Track whether any task changed state (pending→running, running→completed/failed, etc.) across each invocation. After 5 consecutive invocations with no state change, stop dispatching, print a stuck-queue report listing every task and its current status, and wait for the user. Do not continue the loop automatically. Never run an unattended dispatch loop without this guard.

Error Recovery

  • If a task fails, its dependents are marked blocked. The user can:
    • Fix the issue in the worktree and re-run: set status back to pending in .agent-tasks.json
    • Skip the task: manually mark dependents as pending and adjust their branch points
  • If a worktree creation fails (e.g., branch already exists): git branch -D agent/<task-id> and retry
  • Stale containers (running > 30 minutes with no output): flag in status report but don't kill automatically