Layer Execution Agent

SkillProductivity

Handles one layer of task execution. Groups ready tasks into batches, spawns batch agents, and processes merge queue after each batch.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Layer Execution Agent skill

What this skill tells your AI

The instructions your AI receives, as published by vinzenz/prd-breakdown-execute in .claude/skills/execute-layer/SKILL.md and read by ahel’s review.

You manage the execution of a single layer. You build the ready queue, group tasks into batches, spawn batch agents, and coordinate merges.

Input Arguments

Parse these from the prompt:

ArgumentRequiredDescription
--tasks-path <path>YesPath to tasks directory
--layer <name>YesLayer to execute (e.g., "1-foundation")
--project-path <path>YesMain project directory
--worktree-dir <path>YesDirectory for worktrees
--max-parallel <N>NoMax concurrent tasks (default: 3)

Execution Flow

Step 1: Load Layer Plan

Read layer_plan.json to get dependency graph:

cat {tasks_path}/layer_plan.json

Extract:

  • dependency_graph: Map of task_id → list of dependency task_ids
  • layers: List of layer definitions with task lists

Step 2: Load State

Read execute-state.json:

cat {tasks_path}/execute-state.json

Get:

  • completed: List of completed task IDs
  • failed: List of failed task IDs
  • abandoned: List of abandoned task IDs
  • tasks: Individual task status details
  • merge_queue: Current merge queue

Step 3: Build Ready Queue

Find tasks that are ready to execute:

def build_ready_queue(layer, dependency_graph, state):
    ready = []

    for task in layer["tasks"]:
        task_id = task["id"]

        # Skip completed
        if task_id in state["completed"]:
            continue

        # Skip abandoned
        if task_id in state["abandoned"]:
            continue

        # Check dependencies
        deps = dependency_graph.get(task_id, [])
        all_deps_complete = all(d in state["completed"] for d in deps)

        if all_deps_complete:
            ready.append(task_id)

    return ready

Step 4: Update Layer Status

Mark layer as in_progress in state:

{
  "layers": {
    "{layer}": {
      "status": "in_progress",
      "started_at": "{now}",
      "tasks_total": 6,
      "tasks_completed": 0,
      "tasks_failed": 0
    }
  },
  "current_layer": "{layer}"
}

Step 5: Execute Batches Loop

While there are ready tasks:

5a. Build Batch

Group ready tasks up to max_parallel:

batch = ready_queue[:max_parallel]

Assign batch number:

batch_number = current_batch + 1
state["current_batch"] = batch_number
5b. Spawn Batch Agent

Invoke /execute-batch skill:

/execute-batch --tasks-path {tasks_path} --task-ids {comma_separated_ids} --project-path {project_path} --worktree-dir {worktree_dir} --batch-number {batch_number} --layer {layer}

Wait for batch completion.

5c. Handle Batch Result

Parse batch result:

{
  "batch_number": 1,
  "verified": ["L1-001", "L1-002"],
  "failed": ["L1-003"],
  "abandoned": [],
  "should_stop": false
}

If should_stop: true:

  • A task was abandoned (max 5 retries)
  • Report to orchestrator immediately
  • Do NOT process more batches
5d. Process Merge Queue

After each batch, merge verified tasks in order:

for item in merge_queue:
    if item["status"] == "ready":
        # Invoke merge
        invoke_merge(item["task_id"])
        item["status"] = "merged"

Call /execute-merge for each ready task:

/execute-merge --task-id {task_id} --project-path {project_path} --worktree-path {worktree_path} --task-file {task_file}

IMPORTANT: Merge tasks sequentially in priority order to avoid conflicts.

5e. Re-evaluate Ready Queue

After batch completes and merges finish:

  • Some tasks may now have all dependencies satisfied
  • Rebuild ready queue with fresh state
  • Continue loop if tasks remain

Step 6: Update Layer Completion

When no more ready tasks:

{
  "layers": {
    "{layer}": {
      "status": "completed",
      "completed_at": "{now}",
      "tasks_completed": 6,
      "tasks_failed": 0
    }
  }
}

Step 7: Report Layer Result

Output structured result for orchestrator:

{
  "layer": "1-foundation",
  "status": "completed",
  "tasks_total": 6,
  "tasks_completed": 6,
  "tasks_failed": 0,
  "tasks_abandoned": 0,
  "batches_executed": 3,
  "should_stop": false
}

If a task was abandoned:

{
  "layer": "2-backend",
  "status": "stopped",
  "tasks_total": 9,
  "tasks_completed": 5,
  "tasks_failed": 0,
  "tasks_abandoned": 1,
  "abandoned_task": "L2-006",
  "batches_executed": 2,
  "should_stop": true,
  "stop_reason": "Task L2-006 abandoned after 5 attempts"
}

Batch Construction Algorithm

Basic Batching

def build_batches(ready_tasks, max_parallel):
    batches = []
    remaining = list(ready_tasks)

    while remaining:
        batch = remaining[:max_parallel]
        remaining = remaining[max_parallel:]
        batches.append(batch)

    return batches

Example

Ready queue: [L1-001, L1-002, L1-006, L1-003, L1-004, L1-005] Max parallel: 3

Batch 1: [L1-001, L1-002, L1-006] Batch 2: [L1-003, L1-004, L1-005]

Dynamic Re-evaluation

After Batch 1 completes:

  • L1-001, L1-002, L1-006 now complete
  • L1-003 depends on L1-002 → now ready
  • L1-004 depends on L1-003 → still blocked
  • L1-005 depends on L1-003 → still blocked

New ready queue: [L1-003] (only 1 task ready now)

Batch 2 executes with just L1-003.

After Batch 2:

  • L1-003 complete
  • L1-004 now ready
  • L1-005 now ready

Batch 3: [L1-004, L1-005]

Merge Queue Processing

Sequential Merge Order

Tasks complete in parallel but merge sequentially:

Execution Order (parallel):
  L1-001 completes at t=10s
  L1-006 completes at t=15s
  L1-002 completes at t=20s

Merge Order (sequential by priority):
  1. L1-001 (priority 1) → merge at t=21s
  2. L1-002 (priority 2) → merge at t=22s
  3. L1-006 (priority 3) → merge at t=23s

Merge Queue State

{
  "merge_queue": [
    {"task_id": "L1-001", "priority": 1, "status": "merged"},
    {"task_id": "L1-002", "priority": 2, "status": "ready"},
    {"task_id": "L1-006", "priority": 3, "status": "ready"}
  ]
}

Priority Assignment

Assign priority when task is added to merge queue:

def add_to_merge_queue(state, task_id):
    # Priority is order of addition
    next_priority = len(state["merge_queue"]) + 1
    state["merge_queue"].append({
        "task_id": task_id,
        "priority": next_priority,
        "status": "ready"
    })

Error Handling

Batch Agent Failure

If batch agent crashes or times out:

  • Mark all tasks in batch as failed
  • Add to retry queue for next batch
  • Continue with remaining batches

Merge Failure

If merge fails (shouldn't happen with sequential merges):

  • Keep worktree for debugging
  • Mark task as failed
  • Report to orchestrator

State Corruption

If state file is corrupted:

  • Attempt to reconstruct from worktrees and git log
  • If not possible, report error and stop

Output Format

Status Line (Minimal Mode)

[LAYER 1-foundation] Started (6 tasks)
[LAYER 1-foundation] Batch 1/3: L1-001 ✓, L1-002 ✓, L1-006 ✓
[LAYER 1-foundation] Merged: L1-001, L1-002, L1-006
[LAYER 1-foundation] Batch 2/3: L1-003 ✓
[LAYER 1-foundation] Merged: L1-003
[LAYER 1-foundation] Batch 3/3: L1-004 ✓, L1-005 ✓
[LAYER 1-foundation] Merged: L1-004, L1-005
[LAYER 1-foundation] Complete (6/6 tasks)

Final Result

End with:

LAYER_RESULT:
{json object}

The orchestrator parses this to decide next layer or stop.

Dependency Graph Format

From layer_plan.json:

{
  "dependency_graph": {
    "L0-001": [],
    "L0-002": ["L0-001"],
    "L0-003": ["L0-002"],
    "L0-004": ["L0-003"],
    "L1-001": ["L0-004"],
    "L1-002": ["L1-001"],
    "L1-003": ["L1-002"],
    "L1-004": ["L1-003"],
    "L1-005": ["L1-003"],
    "L1-006": ["L0-004"]
  }
}

Reading: L1-003 depends on L1-002. L1-003 cannot start until L1-002 is complete.

Signals

GitHub stars
56
Forks
3
Last commit
Jan 2026
Advanced
Catalog kind
skill
Gateway key
execute-layer
Source
github.com/vinzenz/prd-breakdown-execute