---
name: apt:execute
description: "Execute an implementation plan — build code from subtasks"
apt-skill-version: {{APT_VERSION}}
stage: execute
intent: implement
when_to_use: "A spec + implementation plan already exist under the task directory and the user wants to build the code subtask by subtask with atomic commits."
user_invocable: true
internal: false
spawns_agent: true
agent_name: "apt-executor"
task_context: require-existing
default_execution_mode: auto
execution_modes:
  - auto
  - step
allowed-tools: "Read, Write, Edit, Bash, Grep, Glob"
argument-hint: "apt:execute [--continue] [--subtask <id>] [--parallel] [--wave <N>] [--tdd] [--no-tdd] [--spar]"
gates: 
  - constitution-read
  - constitution-coverage
  - plan-exists
config_keys:
  - coding.tdd_default
  - tdd.iron_law
---
<objective>
Read an implementation plan, iterate through subtasks, implement each one with atomic commits, and track progress. Supports both sequential (default) and wave-parallel execution modes.
</objective>

<your_environment>
- **Working directory:** The project root (where you were invoked)
- **apt-tools path:** `node packages/framework/bin/apt-tools.mjs` or the locally installed `apt-tools` binary
- **Constitution:** Read `AGENTS.md` in the project root if it exists
- **Task directory:** `{task_dir}` — passed from the router. All artifacts are read from/written to here.
- **Plan location:** `{task_dir}/implementation_plan.json` (created by apt:plan)
- **Spec location:** `{task_dir}/spec.md` (created by apt:plan)
</your_environment>

<state_files>
## State Files

**Reads:**
- `AGENTS.md` — Project constitution
- `spec.md` — Requirements specification
- `implementation_plan.json` — Subtask breakdown with statuses
- `build-progress.json` — Resume state (if --continue)
- `.aperant/config.json` — Project config including risk_map patterns (consumed by `node packages/framework/bin/apt-tools.mjs risk-check`) and parallelization settings
- `.aperant/context/notes/{task-id}.md` — **Decision ledger (R6).** If this file exists, read it at the start of execution and pre-load a ≤500-token digest at the top of EVERY subagent/executor spawn prompt (see `<decision_ledger_inject>` below). Contains locked decisions from `/apt:discuss` that fresh subagents would otherwise re-litigate.
- `.aperant/locks/` — Coordination locks (wave-parallel mode)

**Writes:**
- `implementation_plan.json` — Updated subtask statuses
- `build-progress.json` — Execution progress tracking
- `.aperant/events/` — Event log entries (wave-parallel and single-agent fallback modes)
</state_files>

<decision_ledger_inject>
## Decision Ledger Re-Injection (R6)

When `/apt:execute` spawns ANY subagent (executor, wave workers, post-subtask reviewer), you MUST pre-load the decision ledger digest at the top of the subagent's prompt. This prevents fresh agents from re-asking questions the orchestrator already settled.

**Procedure:**
1. Look for `.aperant/context/notes/{task-id}.md` (written by `/apt:discuss`).
2. If it exists, extract a ≤500-token digest of locked decisions. Skip boilerplate; keep only decisions, constraints, and user-confirmed trade-offs.
3. Prepend the digest to every spawn prompt under a clearly delimited block:
   ```
   <locked_decisions source=".aperant/context/notes/{task-id}.md">
   {digest}
   </locked_decisions>
   ```
4. If the file does not exist, skip the inject silently — do NOT fabricate decisions.
5. If the digest exceeds 500 tokens, compact by keeping the most recent decisions and summarizing older ones in one line each.
</decision_ledger_inject>

<arguments>
## Flags

| Flag | Description |
|------|-------------|
| `--continue` | Resume from last checkpoint (reads `build-progress.json`) |
| `--subtask <id>` | Jump to a specific subtask (skip dependency check — user override) |
| `--parallel` | Force wave-parallel mode even if `parallelization.enabled` is false in config |
| `--wave <N>` | Execute only wave N (implies wave mode). Useful for pacing or quota management |
| `--tdd` | Activate the vertical-tracer-bullet TDD discipline for this run (gates `<tdd_iron_law>`). Inverse of `--no-tdd`. Hard-exempt on QUICK tasks per Fast Path Guarantee (ID-05). |
| `--no-tdd` | Deactivate TDD even if `coding.tdd_default: true` is set in config. |
</arguments>

<process>

## 0. Hard Gate Check

Before doing ANY other work, run the hard-gate check for the `execute` skill. This is the first-class enforcement point that replaces earlier soft-prompt reminders about reading AGENTS.md and having a plan.

```bash
node packages/framework/bin/apt-tools.mjs gate check . --task-id {task-id} --skill execute
```

Parse the JSON envelope:
- If `status == "ok"`: proceed to Section 0a below.
- If `status == "fail"`: STOP immediately. Report the failed gates to the user, verbatim from the envelope's `failed:[{id, severity, reason}]` array, then emit the hint:
  ```
  Hard gate failed. Fix the issues above, or re-run with:
    --force --reason "<explanation>"
  The override will be logged to .aperant/events/{today}.jsonl.
  ```
  Do NOT auto-apply `--force` — force is an explicit human decision. Exit the skill.

The gate covers constitution-read (AGENTS.md exists, warn-only), constitution-coverage (every AGENTS.md principle is addressed or explicitly N/A in spec.md — blocking, C14), and plan-exists (spec.md + implementation_plan.json present + parseable — blocking). If any blocking gate fails, there is nothing sensible for the executor to do.

## 0a. Mode Detection

Before loading context, determine execution mode:

1. **Read config:**
   ```bash
   node packages/framework/bin/apt-tools.mjs config .
   ```
   Parse the JSON result. Extract `parallelization.enabled` and `parallelization.max_agents`.

2. **Determine mode:**

   | Condition | Mode |
   |-----------|------|
   | `--wave <N>` flag passed | Wave Mode (execute only wave N) |
   | `--parallel` flag passed | Wave Mode (all waves) |
   | `parallelization.enabled` is `true` AND task track is DEEP | Wave Mode (all waves) |
   | All other cases | Sequential Mode (existing behavior, Sections 1-4) |

3. **If Wave Mode activated:**
   Run wave analysis:
   ```bash
   node packages/framework/bin/apt-tools.mjs wave analyze . [--plan implementation_plan.json]
   ```
   Parse the JSON result: `{ total_waves, waves: [{ wave, subtasks, files }], subtask_wave_map }`.

   - If `total_waves > 1`: proceed to **Section 5: Wave-Parallel Execution**
   - If `total_waves == 1`: all subtasks are independent (single wave). Use sequential mode (Sections 1-4) but with lock/event coordination wrapping (see Section 5d: Single-Agent Fallback)

4. **If Sequential Mode:** proceed to Section 1 below (existing behavior, unchanged).

### 0a.1. TDD Discipline Detection

Compute whether the vertical-tracer-bullet TDD discipline is active for
this run. The `<tdd_iron_law>` block in Section 3c is gated on this
boolean. Three inputs combine; QUICK is hard-exempt regardless.

1. **Read config:** `coding.tdd_default` from `.aperant/config.json`
   (default `false` when absent).
2. **Read flags:** `--tdd` (force-enable) and `--no-tdd` (force-disable)
   on the command line.
3. **Read task track** from `apt-tools task get . --id {task-id}` →
   `task.track`. (If unset, fall back to `complexity` field on
   `implementation_plan.json`: `simple` → QUICK, otherwise STANDARD.)

Compute `tdd_active` as:

```
if track === "QUICK":
  tdd_active = false   # Fast Path Guarantee — ID-05, hard-exempt
elif --no-tdd flag:
  tdd_active = false   # explicit user override
elif --tdd flag:
  tdd_active = true    # explicit user opt-in
else:
  tdd_active = coding.tdd_default || legacy config.tdd.iron_law === true
```

Carry `tdd_active` forward to Section 3c. When `tdd_active === false`,
the iron-law block is skipped entirely and execute behaves identically
to the pre-vertical-slice single-commit flow. When `tdd_active === true`, the
red-green-refactor sequence is mandatory and audited by gate G9.

**The QUICK exemption is a constraint, not a toggle.** A user who passes
`--tdd` on a QUICK-routed task gets `tdd_active = false` and a one-line
note in the run report: "TDD requested but skipped — QUICK Fast Path
Guarantee (ID-05)". The Aperant appendix
`appendices/tdd-mode.md` carries the rationale.

## 1. Load Context

1. Read `AGENTS.md` if it exists — extract coding conventions, test requirements, style rules
2. Read `spec.md` — understand what we're building and acceptance criteria
3. Read `implementation_plan.json` — parse subtask list

```bash
node packages/framework/bin/apt-tools.mjs state .
```

## 2. Determine Starting Point

- **Default:** Find the first subtask with `status: "pending"` whose dependencies are all `"completed"`
- **`--continue` flag:** Resume from where we left off (read `build-progress.json` for last completed subtask)
- **`--subtask <id>` flag:** Jump to a specific subtask (skip dependency check — user override)

If all subtasks are already `"completed"`:
```
All subtasks complete. Nothing to execute.
Run /apt:verify to validate the implementation.
```
Exit.

### Mark lifecycle phase as `executing`

Before starting the subtask loop, flip the task's lifecycle phase. This is the
single point at which a phase-linked task transitions from `planned` →
`in-progress` in its scoped `milestones.json`; the side effect is baked into
`apt-tools task update`, so no effects queue is involved. Quick tasks (no
linkage) silently no-op. Idempotent — safe to re-run on `--continue`.

```bash
node packages/framework/bin/apt-tools.mjs task update . --id {task-id} --lifecycle-phase executing
```

## 3. Execute Subtask Loop

For each pending subtask (in dependency order):

### 3a. Read Current State

Read ALL files listed in `subtask.files` before making any changes. Understand:
- Current file structure and patterns
- Existing interfaces, types, and conventions
- Import patterns and module boundaries
- Test patterns (if test files are in the list)

### 3b. Risk Check

Before implementing the subtask, check risk class of affected files:

```bash
node packages/framework/bin/apt-tools.mjs risk-check . --paths {subtask.files[0]} {subtask.files[1]} ...
```

If the subtask description contains destructive keywords ("delete", "remove", "drop", "force-push"), pass the `--operation` flag:

```bash
node packages/framework/bin/apt-tools.mjs risk-check . --paths {subtask.files} --operation delete
```

Parse the JSON result and apply escalation:

**If risk_class is "Critical" or "Destructive":**
Stop execution. Show the user:
```
RISK GATE: This subtask touches {risk_class} files.
Files: {matched_paths list}
Pattern: {matched_pattern}
Subtask: {subtask.title}

Approve this subtask? (yes/no)
```
Wait for explicit approval. If denied, skip this subtask and mark as `"blocked:risk-gate"` in implementation_plan.json.

This gate is hardcoded and applies at ALL autonomy levels -- Critical and Destructive changes always require human approval.

**If risk_class is "Elevated" AND autonomy < 3:**
Show the subtask plan and affected files before proceeding:
```
ELEVATED RISK: This subtask modifies elevated-risk files.
Files: {matched_paths list}
Plan: {subtask.description}

Proceeding in 5s... (type 'stop' to review first)
```
At autonomy 0-1: wait for explicit approval.
At autonomy 2: show and auto-proceed after brief display.
At autonomy 3 (YOLO): proceed without stopping.

**If risk_class is "Safe" or "Normal":**
Proceed without interruption.

### 3c. Implement Changes

Follow `subtask.description` to implement the changes. Rules:
- One subtask at a time — never work on multiple subtasks simultaneously
- Match existing code patterns (naming, formatting, structure)
- Include error handling appropriate to the codebase style
- Add/update tests if the subtask modifies testable behavior
- Reference `prompts/coder.md` for coding methodology and environment awareness

<tdd_iron_law enabled_when="tdd_active === true">

**TDD Iron Law — conditional flow.** This block activates ONLY when `tdd_active === true` (computed in Section 0a.1 from `coding.tdd_default` config, the `--tdd` / `--no-tdd` flags, and the task track). When `tdd_active === false` — including all QUICK-routed tasks regardless of flag or config (Fast Path Guarantee, ID-05) — skip this block entirely and use the single-commit flow above. The legacy `config.tdd.iron_law` config key continues to work and is rolled into `tdd_active` as a backward-compat alias.

When the iron law is on, 3c/3d/3e collapses into a strict red-green-refactor sequence with **two commits per subtask**:

1. **Write the failing test first.** Identify or create the test file in `subtask.files` (look for `*.test.*` / `*.spec.*`). Add the test that describes the behavior you are about to implement. Do NOT touch implementation files yet.
2. **Commit the red test.**
   ```bash
   node packages/framework/bin/apt-tools.mjs commit "test({scope}): failing test for {subtask.title}" --files {test files}
   ```
3. **Verify the test fails** (RED). Run `{subtask.verification}`. The exit code MUST be non-zero. If it is zero, the test is not actually exercising new behavior — STOP and fix the test before writing any implementation.
4. **Implement.** Now edit the implementation file(s) in `subtask.files` until the test passes. Keep changes minimal — just enough to turn the test green.
5. **Verify the test passes** (GREEN). Re-run `{subtask.verification}`. It MUST now exit zero.
6. **Commit the green implementation.**
   ```bash
   node packages/framework/bin/apt-tools.mjs commit "feat({scope}): {subtask.title}" --files {implementation files}
   ```
   The audit gate `tdd-iron-law` (G9) fires during `/apt:verify` and asserts this commit order per subtask — a `feat(...)` commit that precedes the matching `test(...)` commit on the same file set is a blocking failure.

**Rationalizations to reject** (do NOT take any of these shortcuts when `tdd_active` is on):

| Rationalization | Counter-rule |
|---|---|
| "I'll write the test after I know the impl works." | Then the test only pins what you wrote, not what the spec required. Write it first — from the spec — or the audit gate will fail. |
| "The test would just pin the current code." | That's a *consequence* of writing tests after implementation. Writing them first forces them to describe desired behavior, not incidental behavior. |
| "This subtask is too small for TDD." | The gate still audits it. Even a one-line change gets a `test(...)` commit first. If the subtask has no testable behavior, it should not declare `*.test.*` files in `subtask.files` — the gate will `warn` (not fail) on docs-only or config-only subtasks. |
| "The verification command doesn't support test-first." | Then update the verification command. The plan is editable; the iron law is not. |

</tdd_iron_law>

### 3d. Verify Subtask

Run the verification command from `subtask.verification`:
```bash
{subtask.verification}
```

If verification fails:
- Read the error output
- Fix the issue
- Re-run verification
- If stuck after 3 attempts, reference `prompts/coder_recovery.md` for recovery patterns
- If still stuck, mark subtask as `"blocked"` with a reason and move to next independent subtask

### 3e. Commit and Update State

```bash
node packages/framework/bin/apt-tools.mjs commit "feat: {subtask.title}" --files {space-separated list of changed files}
```

Then atomically sync `implementation_plan.json.subtasks[].status` and
`build-progress.json.completed_subtasks` via the C33 verb. This replaces
the old free-form "update implementation_plan.json: set status to completed"
step — that instruction was ignored under context pressure (see 19-04-26
reference task) and required a force-bypass of the `all-subtasks-complete`
hard gate to ship.

```bash
node packages/framework/bin/apt-tools.mjs task complete-subtask . \
  --task-id {task-id} \
  --subtask-id {subtask.id} \
  --commit $(git rev-parse HEAD)
```

The command is idempotent and supports consolidated ids (`--subtask-id
"E1+E2"` flips both constituent subtasks). It writes both files under a
single file lock.

**If wave mode or coordination is active**, also emit completion event and release lock:
```bash
node packages/framework/bin/apt-tools.mjs event append . --type task-completed --subtask {subtask.id} --data '{"commits":["<commit-sha>"]}'
node packages/framework/bin/apt-tools.mjs lock release . --subtask {subtask.id}
```

### 3e-drift. Committable Drift Check (C56 B3)

After the subtask commit lands, verify no committable `.aperant/` state is uncommitted. Self-staging helpers (C56 B4) cover most CLI writes, but a subtask that touches roadmap/context/decisions/features/project.json manually can still leave drift behind. Running the audit here — not just once in `/apt:ship` — ensures each subtask commit is self-contained and the review pipeline sees coherent state.

```bash
node packages/framework/bin/apt-tools.mjs audit pending-artifacts . --stage
```

- **`committable[]` non-empty after --stage** → the auditor has already `git add`-ed the drift paths. Amend them into the subtask commit (preferred — keeps commit→subtask mapping clean):
  ```bash
  git commit --amend --no-edit
  ```
  Or, if the subtask is already finalized and amending would hide intent, emit a separate one-line bookkeeping commit:
  ```bash
  git commit -m "chore(aperant): capture {subtask.id} artifact drift"
  ```
- **`unknown[]` non-empty** → HARD STOP. A path under `.aperant/` is not classified. Treat as a planner gap — the subtask touched state that the contract does not know about. Write the decision to `{task_dir}/execution-decisions.md` (decision class: `classification-gap`), then either extend `packages/framework/src/cli/artifacts/classification.mjs` inline (if the subtask already modifies framework code) or open a follow-up task (if the drift is orthogonal). Do NOT proceed to the next subtask until the envelope returns `clean`.
- **Envelope status == `clean`** → proceed to 3e-features.

### 3e-features. Features Registry Post-Step (C33)

After the subtask commits, run `features-audit --apply-stubs` so any
newly-added UI/core/hook/IPC file in the diff lands in
`.aperant/features/<area>.json` automatically. The reviewer Pass 6 covers
whatever the post-step missed; running it here closes the gap where
executor commits introduce a new feature surface without registering it.

```bash
node packages/framework/bin/apt-tools.mjs features-audit . \
  --task-id "$TASK_ID" \
  --diff-files "$(git diff --name-only HEAD^ HEAD | tr '\n' ',')" \
  --apply-stubs
```

Notes:
- `--task-id $TASK_ID` (BUG-025b) is mandatory when the executor runs
  from the main-repo cwd instead of inside the worktree (the
  orchestrator's common path). features-audit resolves the worktree's
  `.aperant/features/` from the active task record and writes there,
  preventing generated `*.json` from leaking into main. When invoked
  from the worktree cwd directly, `--task-id` is a no-op (layer 1
  detection short-circuits). Pass `--no-worktree-redirect` only when
  you intentionally want to write to the invoking cwd.
- `--apply-stubs` only runs when the matched surface's policy is
  `auto-update` (the default for `.aperant/features/*.json`). Users who
  opted out via `/apt:setup` Batch 7 get a no-op — the command still
  succeeds.
- Pre-existing flag-drift findings are never emitted here — they're the
  reviewer's job. The post-step is append-only on internal state.
- If the audit fails (exit code != 0), log the failure and continue; don't
  block subtask completion on a features-registry glitch.

### 3e-gate. Per-Task Review Gate (R7)

Read `.aperant/config.json` `per_task_gate` block:

```json
"per_task_gate": {
  "enabled": false,
  "track_filter": ["deep"],
  "max_fix_iterations": 1,
  "mutex_with_wave_mode": true
}
```

Run the gate **only when all of these are true**:
1. `per_task_gate.enabled` is `true`
2. Current task's `track` is in `track_filter` (default: DEEP-only)
3. If `mutex_with_wave_mode` is `true`, wave mode is NOT active (the gate is skipped under wave-parallel execution to avoid cross-subtask reviewer thrash — a full reviewer pass runs once at wave completion instead)

**When the gate fires:**

1. Spawn `apt-reviewer` via the `Agent` tool with `subagent_type: "apt-reviewer"` scoped to the files just committed in step 3e. Pass the task_dir and the list of changed files so the reviewer only audits the current subtask's diff.
2. Reviewer writes findings to `{task_dir}/review-subtask-{subtask.id}.json`.
3. If findings severity >= `major`: apply fixes inline (max `max_fix_iterations` iterations). Re-run the subtask's `verification` command after each fix. Commit the fix with message `fix: address review findings in subtask {subtask.id}`.
4. If fix iterations exhausted without clean review: mark the subtask as `"blocked"` with reason `review-gate-failed` in `implementation_plan.json` and stop the execution loop — do NOT proceed to the next subtask until the user resolves the block.
5. If the reviewer returns `no findings` or only `note`-level findings: proceed to step 3f.

The gate is deliberately opt-in (`enabled: false` by default) because it roughly doubles wall time for DEEP tasks — the quality/speed tradeoff is the user's call via `/apt:setup`.

### 3f. Checkpoint State

`build-progress.json` is written after every subtask in step 3e. That file is the durable resume point for any interruption — auto-compact, crash, user abort, or explicit `/apt:execute --continue`.

**Do not self-estimate context usage. Do not voluntarily exit.** Keep executing subtasks until the plan is complete or a risk gate stops you. The harness handles context management:

- **Claude Code** auto-compacts the query loop when tokens approach the threshold (`src/services/compact/autoCompact.ts`), then the same agent resumes its task with a summarized history. After compaction, re-read `{task_dir}/implementation_plan.json` and `{task_dir}/build-progress.json` and continue from the next pending subtask — the checkpoint is authoritative, your prior in-memory state is not.
- **Other hosts (Codex / Gemini / OpenCode):** if the host terminates mid-task without compaction, the user re-invokes `/apt:execute --continue` and `build-progress.json` provides resume state.

Exiting early because context "feels" full is a bug: it short-circuits auto-compact and forces a human back into the loop for something the harness was going to handle automatically.

## 4. Completion

After all subtasks are completed:

```bash
node packages/framework/bin/apt-tools.mjs commit "build: all subtasks complete" --files implementation_plan.json build-progress.json
```

### Mark lifecycle phase as `verifying`

Flip the task's lifecycle phase at the success-end boundary so the next stage
(`/apt:verify`) finds the task in the correct from-state. Mirror the same CLI
shape as the start-boundary call in Section 2 — quick tasks (no phase linkage)
silently no-op, and the update is idempotent so it is safe to re-run on
`--continue`. Skip this step if any subtask ended in `"blocked"` — only the
all-subtasks-completed branch should advance the phase.

```bash
node packages/framework/bin/apt-tools.mjs task update . --id {task-id} --lifecycle-phase verifying
```

Output:
```
Execution complete:
- Subtasks: {completed}/{total}
- Blocked: {blocked_count} (if any)
- Progress: build-progress.json

Next: /apt:verify
```

### 4a. --spar flag (FRAMEWORK-RFC-001)

If `$ARGUMENTS` contained `--spar`, dispatch `/apt:spar` against the
just-completed implementation before reporting Next.

Topic frame: 1-2 sentences derived from `build-progress.json` summarizing
the diff that just landed (e.g. "N subtasks completed against the M-file
plan; the largest change touched <path>"). If `build-progress.json` is
missing or empty (corner case), use the task description from
`.aperant/state.json` as a graceful fallback.

Same `--timeout-ms 120000` pipeline-mode budget and NO `--rounds` flag
(per FRAMEWORK-BUG-038 — pipeline gates always defer to the skill
default). When merged-config `spar.partner` is set, append `--with <partner>`.

```
/apt:spar --timeout-ms 120000 [--with <partner>] <topic-frame-from-build-progress.json>
```

After spar returns, append the termination headline + Layer 2 path
(`.aperant/spar/<slug>-<ts>.md`) into the report output so the user can
review findings before invoking `/apt:verify`.

## 5. Wave-Parallel Execution

When wave mode is active (determined in Section 0), execution follows the wave structure instead of flat sequential order. This is the throughput multiplier — independent subtasks run in parallel across multiple agent contexts.

### 5a. Wave Analysis

Run wave analysis to get the execution plan:
```bash
node packages/framework/bin/apt-tools.mjs wave analyze . [--plan implementation_plan.json]
```

Parse the JSON result:
```json
{
  "total_waves": 3,
  "waves": [
    { "wave": 1, "subtasks": ["sub-1", "sub-2", "sub-3"], "files": ["src/a.ts", "src/b.ts"] },
    { "wave": 2, "subtasks": ["sub-4", "sub-5"], "files": ["src/c.ts"] },
    { "wave": 3, "subtasks": ["sub-6"], "files": ["src/d.ts"] }
  ],
  "subtask_wave_map": { "sub-1": 1, "sub-2": 1, "sub-3": 1, "sub-4": 2, "sub-5": 2, "sub-6": 3 }
}
```

If `--wave N` was passed, filter to only that wave. Skip waves before N (assume already completed).

### 5b. Per-Wave Execution Loop

For each wave (1 to `total_waves`):

**1. Emit wave-started event:**
```bash
node packages/framework/bin/apt-tools.mjs event append . --type wave-started --subtask "wave-{N}" --data '{"subtask_count": X}'
```
`event append` writes append-only into `.aperant/events/{YYYY-MM-DD}.jsonl` (R10) — every subtask transition (wave-started, task-claimed, subtask-completed, etc.) becomes a line in today's log so cross-agent activity can be replayed after the fact.

**2. Check parallelization settings and worktree state:**
- Read `config.parallelization.max_agents` (default: 3)
- Read `config.parallelization.use_worktrees` (default: false)
- Read the active task record: if `state.active_tasks[taskId].worktree_path` is set, the task already runs inside a task-level worktree (see `task_isolation.worktree_per_task`). Log: "Skipping subtask worktree isolation (already in task worktree)", set `WORKTREE_MODE=false`, and rely on lock coordination only. The task-level worktree provides the isolation this wave would have needed, and trying to nest worktrees would trip the nested-worktree guard in `apt-tools worktree create`.

**2a. (C54) Install preflight — BEFORE running any dep-needing subtask command:**

When `task_isolation.bootstrap_worktree: true` is on, each task has an `install_status` field on its record from the bootstrap hook. Dep-needing subtask commands (`pnpm exec tsc`, `vitest`, `cargo check`, `pytest`, etc.) MUST wait until the install is done — otherwise the first command fails with a spurious "binary not found" that presents as a task failure.

Read `state.active_tasks[<task-id>].install_status`. Branch on the value:
- `undefined` or `"skipped"`: proceed. No install was triggered (legacy / default-config path).
- `"ok"`: proceed. The install succeeded.
- `"running"` or `"queued"`: poll `apt-tools task get . --id <task-id>` every 5 seconds (max 10 polls = 50s cap) until the status transitions to `ok` or `failed`. Log a line each poll: `"Install in progress ([ecosystem], [elapsed]s) — waiting…"`.
- `"failed"`: ABORT the subtask immediately with this exact message (do NOT retry, do NOT fall through to run the command):
  ```
  Worktree install failed at step [ecosystem] — see [install_log_path].
  Fix the install or re-run with --no-bootstrap.
  ```
  Do NOT let the failure surface as a downstream "command not found" — the executor's job here is to make the install failure legible. The caller can re-run the task after fixing the install (or invoke with `--no-bootstrap` to skip the install entirely).

**2b. Continue with the existing worktree-mode logic:**
- Run `node packages/framework/bin/apt-tools.mjs detect-worktree .` — if `is_worktree: true`:
  - Log: "Skipping worktree isolation (already in worktree)"
  - Do NOT create worktrees (prevents nested worktrees)
  - Agents execute in the current working directory directly
  - Note: locks still coordinate correctly (apt-tools uses shared lock dir via git common dir)
- If `use_worktrees` is `true` AND `is_worktree` is `false` AND no task-level `worktree_path` is present: set `WORKTREE_MODE=true`
- Otherwise set `WORKTREE_MODE=false` (agents share working tree with lock coordination only)
- If the wave has more subtasks than `max_agents`, batch them:
  - Batch size = `max_agents`
  - Execute batches sequentially within the wave
  - Each batch runs its subtasks in parallel

**3. For each subtask in this wave:**

  a. **Claim lock:**
  ```bash
  node packages/framework/bin/apt-tools.mjs lock claim . --subtask {subtask-id}
  ```
  - If `status: "already-claimed"` — skip this subtask (another agent has it). Move to next.
  - If `status: "ok"` — this agent now owns the subtask. Proceed.

  b. **Emit task-claimed event:**
  ```bash
  node packages/framework/bin/apt-tools.mjs event append . --type task-claimed --subtask {subtask-id}
  ```

  c. **Create worktree (if WORKTREE_MODE):**

  If `WORKTREE_MODE=true`, create an isolated worktree for this subtask before spawning its agent:
  ```bash
  node packages/framework/bin/apt-tools.mjs worktree create . --subtask {subtask-id} --branch apt/{task-id}/{subtask-id}
  ```
  - Parse the JSON result to get `path` (the absolute worktree directory)
  - The worktree is created at `.aperant/worktrees/{subtask-id}/`
  - The branch `apt/{task-id}/{subtask-id}` is created automatically
  - If creation fails, log the error and fall back to shared working tree for this subtask

  d. **Spawn executor agent:**

  Each subtask gets a fresh agent context pre-loaded with:
  - The subtask description and acceptance criteria
  - The files listed in `subtask.files` (read them first)
  - The `spec.md` for overall context
  - The risk check result for the subtask's files

  If `WORKTREE_MODE=true` and worktree was created successfully:
  - Set the spawned agent's working directory to the worktree `path`
  - Add the worktree isolation prompt (below) to the agent's instructions

  Output spawning instructions for the orchestrator. Each spawned agent receives this prompt:
  ```
  You are executing subtask "{id}" from the implementation plan.

  ## Subtask
  {subtask.description}

  ## Files to modify
  {subtask.files — one per line}

  ## Verification
  {subtask.verification}

  ## COORDINATION RULES
  - You own ONLY the files listed above — do not modify other files
  - Run heartbeat every 30 seconds: node packages/framework/bin/apt-tools.mjs lock heartbeat . --subtask {id}
  - On completion: node packages/framework/bin/apt-tools.mjs event append . --type task-completed --subtask {id} --data '{"commits":["<sha>"]}'
  - On failure: node packages/framework/bin/apt-tools.mjs event append . --type task-failed --subtask {id} --data '{"error":"<description>"}'
  - Release lock when done: node packages/framework/bin/apt-tools.mjs lock release . --subtask {id}
  - Follow all risk gates (Section 3b) — Critical/Destructive files still require approval
  ```

  If `WORKTREE_MODE=true`, append this to the agent prompt:
  ```
  ## WORKTREE ISOLATION
  You are working in an ISOLATED WORKTREE at {path}.
  - Do NOT navigate outside this directory
  - Commit directly to your branch — it will be merged back after completion
  - Your branch: apt/{task-id}/{subtask-id}
  - Lock/event commands still use the main repo's coordination dir automatically
  ```

  Each spawned agent follows the same implementation flow as Section 3 (3a-3e) but scoped to its owned files only.

**4. Wait for wave completion:**

Monitor `.aperant/events/` for `task-completed` or `task-failed` events for all subtasks in this wave.

Poll strategy:
```bash
node packages/framework/bin/apt-tools.mjs event list . --type task-completed --type task-failed
```
Check if all subtask IDs in the current wave have a terminal event. Repeat every 10 seconds until all are resolved.

**5. Merge worktree branches back (if WORKTREE_MODE):**

After all subtasks in the wave have completed or failed, merge their branches back and clean up worktrees.

For each **completed** subtask (has `task-completed` event):
```bash
# Merge the subtask branch into the current branch
git merge apt/{task-id}/{subtask-id} --no-edit

# Remove the worktree
node packages/framework/bin/apt-tools.mjs worktree remove . --subtask {subtask-id}

# Delete the branch (it's been merged)
git branch -d apt/{task-id}/{subtask-id}
```

For each **failed** subtask (has `task-failed` event):
```bash
# Force-remove the worktree (may have dirty state)
node packages/framework/bin/apt-tools.mjs worktree remove . --subtask {subtask-id} --force
```
- Keep the branch `apt/{task-id}/{subtask-id}` for manual investigation
- Note in the failure report: "Branch preserved for inspection: apt/{task-id}/{subtask-id}"

If a merge fails (conflict):
- Log the conflict details (which files conflict, what branches)
- Run `git merge --abort` to restore clean state
- **Do NOT mark the subtask as failed** — the subtask code was completed successfully; only the merge failed
- Force-remove the worktree, keep the branch for manual resolution
- Report prominently:
  ```
  MERGE CONFLICT (subtask completed successfully):
  Subtask {id} finished all work, but merging branch apt/{task-id}/{subtask-id} had conflicts.
  Branch preserved: apt/{task-id}/{subtask-id}
  To resolve manually: git merge apt/{task-id}/{subtask-id}
  ```
- Accumulate as "merge-pending" (not "failed") in the wave completion report

**6. Emit wave-completed event:**
```bash
node packages/framework/bin/apt-tools.mjs event append . --type wave-completed --subtask "wave-{N}" --data '{"completed": X, "failed": Y}'
```

**7. Check results and decide continuation:**
- If **all subtasks completed**: proceed to next wave
- If **some failed but are NOT dependencies for later waves**: continue to next wave, accumulate failures for final report
- If **a failed subtask IS a dependency for a later wave**: stop execution and report:
  ```
  Wave {N} blocked: subtask {id} failed and is a dependency for wave {N+1}.
  Error: {error from task-failed event}
  Failed subtask files: {files}

  Options:
  - Fix the issue and run: /apt:execute --wave {N} (re-run this wave)
  - Skip and continue: /apt:execute --wave {N+1} (may cause cascading failures)
  ```

### 5c. Completion

After all waves complete, gather the full event log:
```bash
node packages/framework/bin/apt-tools.mjs event list .
```

Report:
```
Wave execution complete:
- Waves: {completed}/{total}
- Subtasks: {completed}/{total}
- Failed: {count} (if any, list subtask IDs)
- Events: .aperant/events/

Next: /apt:verify
```

### 5d. Single-Agent Fallback

If `parallelization.enabled` is `false` but `--parallel` flag was passed, OR if `total_waves == 1`:

Execute waves sequentially using a single agent, but still use lock/event coordination. This provides the event log audit trail without requiring multiple agents.

Within each wave, execute subtasks one at a time using the existing Section 3 loop, but with lock claim/release and event emission wrapping each subtask:

```
For each subtask in wave:
  1. node packages/framework/bin/apt-tools.mjs lock claim . --subtask {id}
  2. node packages/framework/bin/apt-tools.mjs event append . --type task-claimed --subtask {id}
  3. Execute subtask (Section 3a-3d)
  4. node packages/framework/bin/apt-tools.mjs commit "feat: {subtask.title}" --files {changed files}
  5. node packages/framework/bin/apt-tools.mjs event append . --type task-completed --subtask {id} --data '{"commits":["<sha>"]}'
  6. node packages/framework/bin/apt-tools.mjs lock release . --subtask {id}
```

This is the recommended first step when enabling parallel coordination — run single-agent with events to verify the coordination layer works before scaling to multiple agents.

### 5e. Worktree Cleanup on Abort

If execution is interrupted (context limit reached, user abort, or unrecoverable error) and `WORKTREE_MODE` was active, clean up all worktrees before exiting:

```bash
# List all active worktrees
node packages/framework/bin/apt-tools.mjs worktree list .
```

For each worktree in the result:
```bash
# Force-remove each worktree (may have uncommitted changes)
node packages/framework/bin/apt-tools.mjs worktree remove . --subtask {subtask_id} --force
```

Report which branches were preserved:
```
Worktree cleanup complete:
- Removed: {count} worktrees
- Branches preserved for inspection: {list of branch names}
- To manually remove a branch: git branch -D apt/{task-id}/{subtask-id}
```

Note: This cleanup runs as a best-effort operation. If the agent context is terminated mid-execution, orphaned worktrees can be cleaned up manually with `git worktree list` and `git worktree remove`.

**Task-level worktrees are out of scope for this cleanup.** When `task_isolation.worktree_per_task` is active, the task's outer worktree is owned by `cmdTask close` (see `apt-tools task close --id {task-id}`), which honours the project's `task_isolation.cleanup_on_close` policy (`ask` | `auto` | `never`). Do not remove the task-level worktree here — leaving it in place is correct so the user can resume or ship without losing work.

</process>
