> Lightweight orchestrator — dispatches isolated phase-agents, tracks state, chains artifacts between phases. Delegates all planning/execution/verification/review to dedicated persona agents.
Scanned 9/11/2026
Install to Claude Code
npx -y skills add Intense-Visions/harness-engineering --skill harness-autopilot --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Harness Autopilot?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/intense-visions-harness-autopilot)More formats (shields.io, HTML) on the badges page.
# Harness Autopilot
> Lightweight orchestrator — dispatches isolated phase-agents, tracks state, chains artifacts between phases. Delegates all planning/execution/verification/review to dedicated persona agents.
## When to Use
- After a multi-phase spec is approved and you want automated phase execution
- When a project has 2+ implementation phases requiring repeated skill invocations
- NOT for single-phase work (use harness-execution directly)
- NOT when the spec is not yet approved (use harness-brainstorming first)
- NOT for CI/headless execution (conversational skill)
## Persona Agents
| Skill | `subagent_type` | State(s) |
| ----------------------- | ----------------------- | -------------------- |
| harness-planning | `harness-planner` | PLAN |
| harness-execution | `harness-task-executor` | EXECUTE |
| harness-verification | `harness-verifier` | VERIFY |
| **harness-integration** | **`harness-verifier`** | **INTEGRATE** |
| harness-code-review | `harness-code-reviewer` | REVIEW, FINAL_REVIEW |
**Iron Law:** Autopilot delegates, never reimplements. If writing plan/execute/verify/review logic, STOP — delegate via `subagent_type`. Always use dedicated persona agents, never general-purpose agents.
### Lifecycle skill hooks (project-declared) — `skillHooks`
A project may attach **additional skills, commands, and prompts** at lifecycle points of any hook-supporting skill via the top-level `skillHooks` block in `harness.config.json`. This is the general framework; autopilot is its flagship consumer (the review case below is the primary worked example). Resolution/normalization is shared in `@harness-engineering/core` (`resolveSkillHooks` + the hook input-context helpers) so every hook-supporting skill honors the same contract.
```jsonc
// harness.config.json
{
"skillHooks": {
"harness-autopilot": {
"before:EXECUTE": [
"preflight-skill", // bare string = a `skill` hook (shorthand)
{ "type": "command", "run": "pnpm lint", "blocking": true },
{
"type": "prompt",
"text": "Prefer existing helpers in packages/core/util over new ones.",
},
],
"after:REVIEW": [{ "type": "skill", "skill": "canary-cassandra", "blocking": true }],
"after:FINAL_REVIEW": ["canary-cassandra"],
"on:failure": [{ "type": "command", "run": "scripts/notify.sh" }],
},
},
}
```
**Autopilot's hookable event vocabulary.** Event keys follow the grammar `^(before|after|on):[A-Za-z0-9_-]+$`. Autopilot's phase states are `PLAN`, `EXECUTE`, `VERIFY`, `INTEGRATE`, `REVIEW`, and `FINAL_REVIEW`. Hooks are **wired today at exactly four points**: `before:EXECUTE`, `after:REVIEW`, `after:FINAL_REVIEW`, and `on:failure` (at the failure path — see EXECUTE recovery and the review hard-halt). The remaining phase boundaries (`before:PLAN`/`after:PLAN`, `after:EXECUTE`, `before:VERIFY`/`after:VERIFY`, `before:INTEGRATE`/`after:INTEGRATE`, `before:REVIEW`) share the same grammar and resolver but are **not yet wired** — a hook configured at an unwired event is a **no-op** (silently skipped, not hard-halted) until that boundary is wired, which is a documented follow-up. Configure only the four wired events for guaranteed firing; the hard-halt false-green protection applies at those wired points.
**Three hook kinds.** Each array entry is a bare skill-name string (shorthand for a `skill` hook) or one of three discriminated objects. All object kinds accept `"enabled": false` to park a hook without deleting it (a disabled hook is skipped — never a hard halt):
- **`skill`** — `{ "type": "skill", "skill": "<name>", "blocking"?: bool }`. Dispatch that skill as an additional subagent (the LLM path). Its brief MUST include the same context the built-in persona/reviewer gets (see Hook context).
- **`prompt`** — `{ "type": "prompt", "text": "<text>" }`. Mechanically APPEND `text` to that phase's persona-agent prompt/context. Purely declarative — runs no process, has no pass/fail, always non-blocking. (Static text in v1; `{{token}}` templating is RESERVED v2.)
- **`command`** — `{ "type": "command", "run": "<shell>", "blocking"?: bool }`. Mechanically RUN the command at the hook point via the harness command-runner (honoring cwd), capturing exit code + stdout/stderr into the phase record. Deterministic, no LLM.
**Blocking & hard-halt policy (per kind).**
- **`skill`** — an unresolvable/undispatchable skill (typo, not installed) is a **HARD HALT**, not a silent skip and not an overridable finding: record it in `.harness/failures.md`, set the stage status to failed, and stop (resumable once config is fixed). Findings block per the default-blocking policy (review/verify events default `blocking:true`, else `false`; a per-entry `blocking` overrides).
- **`command`** — distinguish two failure modes: **(a)** the command CANNOT be spawned (binary missing / spawn error) = **HARD HALT**, same class as an unresolvable skill; **(b)** the command RAN and exited non-zero = a normal **finding**, blocking per the default-policy/override (NOT a hard halt).
- **`prompt`** — never blocks and never halts (it is only text; an empty `text` is a config-validation error).
**Hook context (input contract).** When autopilot fires a hook it threads the invocation context, assembled once by the shared module:
| Env var (for `command`) | Meaning |
| ------------------------ | ---------------------------------------- |
| `HARNESS_HOOK_EVENT` | the event key, e.g. `after:REVIEW` |
| `HARNESS_HOOK_SKILL` | the host skill, e.g. `harness-autopilot` |
| `HARNESS_PHASE` | the current phase/state, when known |
| `HARNESS_PROJECT_ROOT` | absolute project root |
| `HARNESS_SESSION_DIR` | the session directory, when known |
| `HARNESS_CHANGED_FILES` | newline-separated list of changed files |
| `HARNESS_PLAN_PATH` | the active plan path, when known |
| `HARNESS_FAILURE_REASON` | set only on `on:failure` |
- **`command`** hooks receive this context as those env vars AND the same context as a JSON object on **stdin** for structured consumers. Absent values are UNSET env keys (no empty-string placeholders). Conditional/glob scoping is achievable today by a command self-gating on `$HARNESS_CHANGED_FILES` (a dedicated conditional-glob field is RESERVED v2).
- **`skill`** hooks receive the same context (event, session dir, changed files, plan path) in the subagent brief, reusing whatever context the built-in `harness-code-reviewer` dispatch already threads.
- **`prompt`** hooks are the static text, appended verbatim.
**Generic dispatch pattern (one pattern, every event).** At each hookable point, resolve and run the project's hooks:
1. `hooks = resolveSkillHooks(config, "harness-autopilot", "<before|after|on>:<STATE>")` (empty ⇒ no-op; absent/empty config preserves today's exact behavior — no regression).
2. For each hook in order, by kind: **`prompt`** → append `text` to this phase's persona prompt; **`command`** → run via the command-runner with the hook env + JSON stdin (cannot-spawn ⇒ hard halt; ran-and-non-zero ⇒ finding); **`skill`** → dispatch as an additional subagent with a brief carrying the hook context.
3. Merge `command`/`skill` findings into this phase's aggregation, honoring each hook's `blocking`.
**Extension contract — how a skill becomes hook-supporting.** (1) Declare its event vocabulary in its own SKILL.md; (2) at each lifecycle point call `resolveSkillHooks(config, "<this-skill>", "<event>")`; (3) honor the blocking + hard-halt rules and pass the hook input-context. `harness-autopilot` and `harness-code-review` are the wired reference consumers; remaining skills (brainstorming, fleets, ...) follow as a follow-up.
**RESERVED (v2).** Per-iteration granularity (`after:EXECUTE:task`, `after:dispatch:item` — a phase-level hook fires once per phase, not per item), a `"*"` wildcard outer key ("hooks for every skill"), and `{{token}}` prompt templating.
**Canary detectors auto-wire at REVIEW / FINAL_REVIEW (default, zero-config, forward-wired).** When canary is present, autopilot's two review moments run canary's **deterministic test detectors** — `canary-savant` (test order-dependence / shared-state leakage), `canary-blackhawk` (temporal dependence: wall-clock, timezone, DST, Feb 29), `canary-katana` (tests deleted or newly skipped by the change), and `canary-cassandra` (vacuous tests / assertions that cannot fail) — **alongside `harness-code-reviewer`, never replacing it**. These are the SPECIFIC detectors named by the feature request (distinct from a general-purpose test reviewer, which finds brittleness/anti-patterns but structurally cannot find a self-comparing assertion or an order-dependent pass). This is an **additional default layer**, not new config: it fires only when canary is detected, reusing the exact `skillHooks` dispatch/context path.
> **Forward-wired — these are harness-OPTIMISTIC defaults, not guaranteed-installed skills.** As of canary **5.12.0** the plugin ships **none** of the four (its review-adjacent skills are `canary-test-reviewer`, `canary-pr-guardian`, `canary-ci-ready`, `canary-critical-areas`, …). So today, in a canary-present project, **all four are skipped and REVIEW proceeds normally** on the baseline reviewer alone. Each detector **auto-activates** if/when canary ships it and it reports as installed. This is why a not-installed canary default is a **graceful skip, never a hard halt** — the opposite of a user's typo'd hook.
- **Detect canary once per review.** Call the `canary_probe` MCP tool; `status: "available"` ⇒ canary present. `degraded`/absent ⇒ canary absent, and the effective hooks are exactly today's configured `skillHooks` (no regression).
- **Determine which detectors are installed.** Check the available skill catalog for each of the four detector names (the same way you know whether any other skill is dispatchable). Pass the installed set as `availableSkills` below — this is what makes the wiring forward-compatible and resolve-and-filter, rather than optimistic-and-halt.
- **Resolve effective review hooks via the merged resolver.** Instead of the bare `resolveSkillHooks` at `after:REVIEW` / `after:FINAL_REVIEW`, autopilot uses `resolveReviewHooksWithCanary(config, "harness-autopilot", "<after:REVIEW|after:FINAL_REVIEW>", { canaryPresent, availableSkills })` (from `@harness-engineering/core`). It returns the project's configured hooks FIRST, then appends only the **installed** canary detectors, dropping any detector the project already declares (the explicit entry wins — no double dispatch). Use `planCanaryReviewDetectors(canaryPresent, event, availableSkills)` to get the `{ wired, skipped, expected }` split for the denominator report. Wired detectors are ordinary blocking `skill` hooks, dispatched and aggregated through the **same** path as any configured hook.
- **A not-installed canary default detector is SKIPPED, never a hard halt.** Because a canary default is harness-optimistic (forward-wired), a detector whose skill is not installed is filtered out by the resolver and simply recorded as skipped. It must NOT set the review to failed and must NOT halt. This is distinct from the **user-declared `skillHooks` hard-halt** below.
- **A USER-declared unresolvable hook still HARD-HALTS.** A skill a project explicitly lists in `skillHooks` (including a detector name typed by hand) that cannot be dispatched is the same false-green class as any configured `skill` hook: record it in `.harness/failures.md`, set the review status to failed, and halt with "cannot verify". The distinction is intent: harness-optimistically-defaulted-but-not-yet-shipped = skip; user-declared-but-unresolvable = hard halt.
- **A project can override or park any detector.** Re-declaring a detector in `skillHooks` (e.g. `{ "type": "skill", "skill": "canary-cassandra", "blocking": false }`) overrides its `blocking`; declaring it `{ ..., "enabled": false }` parks it entirely (the `enabled: false` contract applies to the auto-wired defaults too — a parked detector is skipped, never re-injected). Dedup is by raw declared name, so both cases suppress the default.
- **Report the denominator, including skips.** State how many detectors ran of how many were expected and why any were skipped (e.g. "canary detectors: 0/4 ran — 4 skipped: not installed in this canary version"), so a partial or forward-wired run is never presented as a full one.
- Detectors wire **only** at `after:REVIEW` and `after:FINAL_REVIEW`; no other phase boundary gains them.
## Rigor Levels
Set at INIT (`--fast` / `--thorough`); persists for session. Default: `standard`.
| State | `fast` | `standard` | `thorough` |
| ------------ | -------------------------- | --------------------- | ----------------------------- |
| PLAN | Skip skeleton pass | Default | Always skeleton with approval |
| APPROVE_PLAN | Auto-approve, skip signals | Signal-based | Force human review |
| EXECUTE | Skip scratchpad | Scratchpad >500 words | Verbose scratchpad |
| VERIFY | `harness validate` only | Full pipeline | Expanded checks |
| INTEGRATE | WIRE only, auto-approve | Full tier-appropriate | Full + human ADR review |
## State Machine
```
INIT → ASSESS → PLAN → APPROVE_PLAN → EXECUTE → VERIFY → INTEGRATE → REVIEW → PHASE_COMPLETE
│
[next phase?]
│ │
ASSESS FINAL_REVIEW → OUTCOME_EVAL → DONE
```
---
### INIT
1. Resolve spec path (argument or prompt).
2. Derive session slug: strip `docs/`, drop `.md`, replace `/` and `.` with `--`, lowercase. Set `sessionDir = .harness/sessions/<slug>/`.
3. Check for existing state: read `{sessionDir}/autopilot-state.json`. If present and not DONE: report "Resuming from `{currentState}`, phase {N}: {name}." Apply schema migration if `schemaVersion < 5` (backfill missing fields). Jump to recorded state.
4. Fresh start: read spec, parse `## Implementation Order` for phases (`### Phase N: Name` + `<!-- complexity: low|medium|high -->`, default: `medium`). Capture `startingCommit` via `git rev-parse HEAD`. Write `autopilot-state.json` (schemaVersion: 5, currentState: "ASSESS", currentPhase: 0).
5. Flags: `--fast` → `rigorLevel: "fast"`. `--thorough` → `rigorLevel: "thorough"`. `--review-plans` → `reviewPlans: true`. Both flags together → reject with error.
6. Call `gather_context({ path, skill: "harness-autopilot", session: slug, include: ["state", "learnings", "handoff", "graph", "businessKnowledge", "sessions", "validation"] })`.
7. → ASSESS.
---
### ASSESS
1. Read current phase at `currentPhase`.
2. If `planPath` set and file exists: → APPROVE_PLAN.
3. **Intelligence-enhanced complexity assessment.** Before routing by complexity, refine the annotation with signals from available tools:
- Run `predict_failures` on the phase domain to check if constraints are trending toward violation — high failure probability suggests upgrading complexity.
- Run `compute_blast_radius` on files the phase is likely to touch — large blast radius (>15 affected modules) suggests upgrading to `high`.
- If the orchestrator is running, request intelligence analysis via `POST /api/analyze` with the phase title/description to get CML complexity scores and PESL simulation results. Use CML `structuralComplexity > 0.7` or PESL `riskScore > 0.6` as triggers to upgrade complexity routing.
- If no orchestrator, the MCP tool signals above are sufficient.
4. Complexity routing:
- `low`/`medium`: auto-plan via harness-planner → PLAN.
- `high`: pause. Instruct: "Run `/harness:planning` interactively, then re-invoke `/harness:autopilot`." Wait for re-invocation.
5. Update `currentState: "PLAN"`.
---
### PLAN
**Auto-plan (low/medium):** Dispatch harness-planner:
```
subagent_type: "harness-planner"
prompt: "Phase {N}: {name}. Spec: {specPath}. Session: {sessionSlug}. Rigor: {rigorLevel}. Follow harness-planning. Write plan to docs/changes/<topic>/plans/ (topic from specPath; legacy docs/plans/ if spec is outside docs/changes/). Write {sessionDir}/handoff.json when done."
```
On return: read `planPath` from `{sessionDir}/handoff.json`. Complexity override check: `low` + tasks>10 or checkpoints>3 → `"medium"`; tasks>20 or checkpoints>6 → `"high"`. Update state `planPath`. → APPROVE_PLAN.
**Interactive plan (high):** Check for plan file at `docs/changes/<topic>/plans/*{phase-name}*` (or legacy `docs/plans/*{phase-name}*`) or `planPath` in handoff. If found: update `planPath` → APPROVE_PLAN. If not: remind and wait.
---
### APPROVE_PLAN
1. Gather: task count, checkpoint count, concerns from `{sessionDir}/handoff.json` (default `[]`).
2. `"fast"` → auto-approve, record `"auto_approved_plan_fast"`, → EXECUTE.
3. `"thorough"` → force `shouldPauseForReview = true`.
4. Signals (any true → pause; all false → auto-approve):
- `reviewPlans: true`
- `phase.complexity === "high"`
- `phase.complexityOverride !== null`
- Handoff `concerns` non-empty
- Task count > 15
- Knowledge gaps: `harness knowledge-pipeline --domain <phase-domain>` reports `totalGaps > 0` and `--fix` was not run during planning
5. **Auto-approve:** emit report (mode, complexity, concerns, task count). Record decision with signal snapshot in `decisions[]`. → EXECUTE.
6. **Pause:** show triggered signals. Ask "Approve? (yes / revise / skip phase / stop)." Record decision. Route accordingly.
---
### EXECUTE
**Pre-dispatch: `before:EXECUTE` hooks.** Resolve `resolveSkillHooks(config, "harness-autopilot", "before:EXECUTE")` and run each hook in order by kind (see Lifecycle skill hooks): a `prompt` hook appends its text to the task-executor brief; a `command` hook runs via the command-runner with the hook env + JSON stdin (cannot-spawn ⇒ hard halt to `.harness/failures.md`; ran-and-non-zero ⇒ a finding, blocking per its policy — surface it before dispatching); a `skill` hook runs as a pre-flight subagent. This is the non-review generality proof: hooks fire at a non-review phase too. Empty/absent ⇒ no-op.
**Pre-dispatch: plan parallelization (standard automatic parallelism).** Before dispatching tasks, decide the safe parallel structure. This is orchestration, not reimplementation — autopilot chooses HOW to dispatch; the persona agents still do the work.
1. Collect the phase's tasks with their `files`, `dependsOn`, and `owns` (from the plan's task headers — `owns` comes from the optional `**Owns:**` line). Call the `plan_parallelization` MCP tool:
```json
{
"path": "<project-root>",
"tasks": [{ "id": "task-1", "files": ["..."], "dependsOn": [], "owns": ["src/api/**"] }],
"depth": 1
}
```
It returns a `ParallelizationPlan`: `waves[]` (each `{ tasks, severity, firing, analysisLevel }`), `serialized[]`, `cyclic[]`, `narration`, and `ownershipForecast`. Forwarding each task's `owns` lets the cheap deterministic owns-overlap check contribute implicit edges to the wave DAG and surface any overlapping ownership pairs in `ownershipForecast`. Omit `owns` for tasks that declare none — it stays a no-op.
2. **If `cyclic` is non-empty:** STOP. Surface the cycle and route back to PLAN/APPROVE_PLAN (a dependency cycle is a plan defect). Do not dispatch.
3. **Announce (announce-and-proceed):** emit `narration` verbatim. Do NOT pause here — announcing is not a gate.
4. **Dispatch in dependency order — prerequisites first, then waves in array order:**
- First, run every task in `serialized` (high-severity-group / cycle-adjacent members) **serially**, one `harness-task-executor` per task, in listed order. These are cross-bucket prerequisites: they MUST complete before any wave that depends on them.
- Then process `waves` **in array order**. The `waves` array is already topologically sorted (earlier waves are prerequisites of later ones). Do NOT reorder waves and do NOT key dispatch off the `firing` field alone — the Phase-2 cross-bucket cap (a wave depending on a serialized/cyclic task is downgraded to `confirm` and marked "cross-bucket prerequisite gates this wave" in `narration`) is only sound if serialized/cyclic ran first and waves run in order.
5. **For each wave, honor its `firing`:**
- `auto-dispatch` (multi-task): emit that wave's line from `narration`, then dispatch the wave via the **harness-parallel-agents** skill with **worktree-per-unit isolation** per `docs/guides/agent-worktree-patterns.md` ("Worktree-per-Milestone" / "Parallel Agent Work": one worktree per task, sequential commits, squash-merge at integrate). **Announce and proceed — do NOT stop for confirmation.**
- `confirm`: surface the wave and its `narration` line, then take exactly ONE plain-text confirmation — "Dispatch wave [{tasks}] in parallel? (yes / serial)". `yes` → dispatch via harness-parallel-agents (worktree-per-unit). `serial` or decline → run the wave's tasks serially (`harness-task-executor` each). Record the choice in `decisions[]`.
- `serialize`, or any single-task wave: run serially via `harness-task-executor`, exactly as today.
6. **Serial fallback is preserved** when a phase has fewer than `minWaveSize` (default 3) independent tasks, when a `confirm` is declined, or when no graph is available and the human does not confirm — honoring the standing "when in doubt, run serially" default.
Each dispatched unit (parallel wave or serial task) then follows the existing per-task contract below (state.json per task, checkpoints, retry budget).
Dispatch harness-task-executor:
```
subagent_type: "harness-task-executor"
prompt: "Phase {N}: {name}. Plan: {planPath}. Session: {sessionSlug}. Rigor: {rigorLevel}. Update {sessionDir}/state.json per task. Write {sessionDir}/handoff.json when done or blocked."
```
**Checkpoints:** `[checkpoint:human-verify]` → show output, confirm, resume. `[checkpoint:decision]` → present options, record choice, resume. `[checkpoint:human-action]` → instruct user, wait for confirmation, resume. After each passing checkpoint: `commitAtCheckpoint()`.
**Outcome:** All tasks complete → VERIFY. Task fails → retry logic:
- Attempt 1: read error, apply obvious fix, re-dispatch for failed task.
- Attempt 2: expand context — read related files, check `learnings.md`, re-dispatch.
- Attempt 3: full context — test output, imports, plan instructions, re-dispatch.
- Budget exhausted: recovery commit (`[autopilot][recovery]` prefix in message), record in `.harness/failures.md`. **Fire `on:failure` hooks** — `resolveSkillHooks(config, "harness-autopilot", "on:failure")` — passing the hook context with `HARNESS_FAILURE_REASON` set to the failure summary (a `command` hook e.g. `scripts/notify.sh` is the natural use; these are advisory and never block the failure path). Then ask: "fix manually and continue / revise plan / stop." The same `on:failure` fan-out runs whenever autopilot writes a hard-halt/failure to `.harness/failures.md` (e.g. an unresolvable review hook).
---
### VERIFY
- `"fast"`: run `harness validate`. Pass → INTEGRATE. Fail → surface to user.
- `"standard"`/`"thorough"`: dispatch harness-verifier:
```
subagent_type: "harness-verifier"
prompt: "Phase {N}: {name}. Session: {sessionSlug}. Rigor: {rigorLevel}. Verify and report pass/fail with findings."
```
Pass → INTEGRATE. Fail → ask "fix / skip verification / stop." `fix`: re-enter EXECUTE (retry budget resets).
---
### INTEGRATE
1. Resolve tier: `max(plan.integrationTier, derived-from-execution)`. If tier escalated: notify human with "Tier escalated from `{planned}` to `{derived}`: {reason}."
2. Dispatch harness-integration skill:
```
subagent_type: "harness-verifier"
prompt: "Phase {N}: {name}. Session: {sessionSlug}. Tier: {tier}.
Plan: {planPath}. Verify integration per harness-integration skill."
```
3. **Rigor interaction:**
- `"fast"`: WIRE sub-phase only, auto-approve, no ADR drafting.
- `"standard"`: Full tier-appropriate checks (WIRE + MATERIALIZE + UPDATE per tier).
- `"thorough"`: Full checks + human reviews every ADR draft + force knowledge graph verification.
4. Pass → REVIEW.
5. Fail → report incomplete items. Ask "fix / skip integration / stop":
- **fix:** re-enter EXECUTE with integration-specific fix tasks, then re-VERIFY, re-INTEGRATE. Retry budget resets.
- **skip:** record decision in `decisions[]`, proceed to REVIEW (human override).
- **stop:** save state and exit.
---
### REVIEW
Dispatch harness-code-reviewer:
```
subagent_type: "harness-code-reviewer"
prompt: "Phase {N}: {name}. Session: {sessionSlug}. Follow harness-code-review. Report findings (critical / important / suggestion)."
```
**Then run each effective `after:REVIEW` hook.** Probe canary once (`canary_probe`; `status: "available"` ⇒ present), determine which of the four detectors are installed, and resolve `resolveReviewHooksWithCanary(config, "harness-autopilot", "after:REVIEW", { canaryPresent, availableSkills })` (from `@harness-engineering/core`). This returns the project's configured `skillHooks` (default none) followed by canary's **installed** deterministic detectors when canary is present — see "Canary detectors auto-wire" under Lifecycle skill hooks. When canary is absent it returns exactly the configured hooks (no regression). Run each hook in order by kind (see Lifecycle skill hooks): a `skill` hook is dispatched as an extra reviewer, a `command` hook runs via the command-runner, a `prompt` hook appends its text to the review brief. For a `skill` hook:
```
subagent_type: "<hook skill name>"
prompt: "Phase {N}: {name}. Session: {sessionSlug}. Domain review. Context: {hook context}. Report findings (critical / important / suggestion)."
```
If a **user-declared** hooked skill cannot be dispatched (typo / not installed), or a `command` hook cannot be spawned (missing binary), do **not** skip it silently and do **not** offer it as an overridable finding: record it in `.harness/failures.md`, set the review status to failed, and halt REVIEW with "cannot verify" — a **hard halt** (see Lifecycle skill hooks, above). (A `command` that RAN and exited non-zero is instead a normal finding, blocking per policy — not a hard halt.) A **harness-default canary detector** that is not installed is the exception: it was already filtered out by `resolveReviewHooksWithCanary` (forward-wired, graceful skip) and is recorded as skipped in the denominator, never a hard halt. Persist each hook's findings under a per-hook key in `{sessionDir}/phase-{N}-review.json` so provenance survives, and merge them into the same blocking/non-blocking aggregation as the baseline reviewer. Report the detector denominator (how many hooks ran of how many were expected, and how many were skipped-not-installed) so a partial or forward-wired run is never presented as complete.
Persist findings to `{sessionDir}/phase-{N}-review.json`. No blocking (across baseline + additional reviewers) → PHASE_COMPLETE. Blocking → ask "fix / override / stop." `fix`: re-enter EXECUTE. `override`: record decision in `decisions[]` → PHASE_COMPLETE.
---
### PHASE_COMPLETE
1. Present summary: name, tasks completed, retries used, verification result, integration report (`{sessionDir}/phase-{N}-integration.json`), review findings count, elapsed time.
2. Record in `history[]`: phase index, name, startedAt, completedAt, tasksCompleted, retriesUsed, verificationPassed, integrationPassed, reviewFindings.
3. Mark phase `complete` in state. Clear scratchpad: `clearScratchpad({ session, phase, projectPath })`.
4. Sync roadmap: `manage_roadmap sync apply:true` (skip if no roadmap; never `force_sync: true`).
5. Write session summary: `writeSessionSummary(projectPath, sessionSlug, { session, lastActive, skill: "harness-autopilot", phase, status, spec, plan, keyContext, nextStep })`.
6. More phases: "Phase {N} complete. Next: {N+1}: {name} ({complexity}). Continue? (yes / stop)." `yes` → increment `currentPhase`, reset `retryBudget`, → ASSESS. `stop` → save and exit.
7. No more phases: → FINAL_REVIEW.
---
### FINAL_REVIEW
1. Set `currentState: "FINAL_REVIEW"`, `finalReview.status: "in_progress"`.
2. Gather per-phase findings from `{sessionDir}/phase-{N}-review.json` files.
3. Dispatch harness-code-reviewer:
```
subagent_type: "harness-code-reviewer"
prompt: "Final cross-phase review. Diff: git diff {startingCommit}..HEAD. Session: {sessionSlug}. Prior findings: {collected}. Focus on cross-phase coherence: naming, duplicated utilities, architectural drift. Report findings (critical / important / suggestion)."
```
Then run **each effective `after:FINAL_REVIEW` hook** — `resolveReviewHooksWithCanary(config, "harness-autopilot", "after:FINAL_REVIEW", { canaryPresent, availableSkills })` (configured `skillHooks` plus canary's **installed** deterministic detectors when canary is present; exactly the configured hooks when absent) — over the same cross-phase diff. Run each by kind; a `skill` hook is dispatched as an extra reviewer:
```
subagent_type: "<hook skill name>"
prompt: "Final cross-phase domain review. Diff: git diff {startingCommit}..HEAD. Session: {sessionSlug}. Context: {hook context}. Report findings (critical / important / suggestion)."
```
An unresolvable **user-declared** hooked skill (a typo, a not-installed declared skill) or an un-spawnable `command` hook is a FINAL_REVIEW failure, not a skip, and not an overridable finding — record it in `.harness/failures.md`, set the final-review status to failed, and hard-halt with "cannot verify" rather than reporting a green final review that silently dropped a configured hook. A harness-default canary detector that is not installed is instead forward-wired: `resolveReviewHooksWithCanary` already filtered it out, so it is a graceful skip recorded in the denominator, never a FINAL_REVIEW failure. (A `command` that ran and exited non-zero is a normal blocking finding, not a hard halt.) Hook findings merge into the same `finalReview.findings` aggregation; report the detector denominator (including skipped-not-installed) so a partial or forward-wired run is never presented as complete.
4. No blocking (across baseline + additional reviewers): store in `finalReview.findings`, set `"passed"` → OUTCOME_EVAL.
5. Blocking: ask "fix / override / stop."
- `fix`: increment `finalReview.retryCount` (max 3). Dispatch harness-task-executor: "Fix these blocking findings: {findings with file, line, title}. Session: {sessionSlug}. Commit each fix atomically." Run `harness validate`. Re-run FINAL_REVIEW from step 1. If retryCount > 3: stop, record in `.harness/failures.md`.
- `override`: record rationale in `decisions[]`. Set `"overridden"` → OUTCOME_EVAL.
- `stop`: save state and exit (resumable).
---
### OUTCOME_EVAL
The blocking post-execution **spec-satisfaction gate** — the harness's ship gate. It fires ONCE per session, after FINAL_REVIEW and before DONE, judging the WHOLE change against the spec. (It runs at the ship boundary, not per phase: a per-phase judgment would spuriously fail while later phases are still outstanding.) It runs at every rigor level — the gate is not weakened by `--fast`.
1. Set `currentState: "OUTCOME_EVAL"`.
2. **Gather the evidence (required).** Capture the cumulative change as a unified diff: `git diff {startingCommit}..HEAD`. Capture the most recent test-runner output retained from VERIFY; if none is retained, re-run the project test command and capture stdout+stderr. Resolve the head sha via `git rev-parse HEAD`.
3. **Invoke the gate.** Call the `outcome_eval` MCP tool:
```json
{
"specPath": "<spec path>",
"diff": "<git diff {startingCommit}..HEAD>",
"testOutput": "<captured test output>",
"commit": "<git rev-parse HEAD>"
}
```
Supplying real `diff` and `testOutput` is mandatory — omitting them degrades the verdict to INCONCLUSIVE/advisory, a silent false-negative that defeats the gate. The tool persists the verdict as an `execution_outcome` node (keyed by `commit`) and returns `verdict`, `confidence`, `judgedAgainst`, `rationale`, `unmetCriteria`, and the TS-derived `authority`.
4. **Honor the TS-derived authority — never the LLM.** `authority` is computed in TypeScript from `(verdict, confidence)`; read it, never recompute or override it.
- `authority === "advisory"` (all `SATISFIED`, all `INCONCLUSIVE`, and every `medium`/`low` `NOT_SATISFIED`): record the verdict in `finalReview` and proceed → DONE. An advisory `NOT_SATISFIED` is surfaced for human attention but does not halt.
- `authority === "blocking"` (a high-confidence `NOT_SATISFIED`, and ONLY that): HALT before DONE. Report `unmetCriteria` and ask "fix / override / stop."
- `fix`: increment `finalReview.retryCount` (max 3). Dispatch harness-task-executor to address the unmet criteria, run `harness validate`, then re-enter OUTCOME_EVAL. If retryCount > 3: stop, record in `.harness/failures.md`.
- `override`: record the rationale in `decisions[]` — the audit trail for shipping over a blocking verdict → DONE.
- `stop`: save state and exit (resumable).
5. **Never block on infrastructure noise.** A provider failure, a missing/unjudgable spec, or an absent `ANTHROPIC_API_KEY` degrades to `INCONCLUSIVE`/advisory and proceeds — surface the degradation to the human, but do not halt.
---
### DONE
1. Present: total phases, tasks, retries, time, `finalReview.status` + findings count, any overridden findings, and the OUTCOME_EVAL verdict (`verdict` / `confidence` / `authority`, plus any recorded override).
2. Ask "Create a PR? (yes / no)." When creating the PR, include a bare closing line `Closes #<N>` where `<N>` is the issue number from the roadmap row's `External-ID`. The keyword MUST sit IMMEDIATELY before the ref — no intervening words (`Closes #<N>`, never `Closes roadmap #<N>`), or GitHub will not link/close the issue and roadmap auto-done will skip the row.
3. Write final handoff to `{sessionDir}/handoff.json`. Append learnings to `.harness/learnings.md`. Call `promoteSessionLearnings(projectPath, sessionSlug)`. If learnings count > 30, suggest `harness learnings prune`.
4. If `docs/roadmap.md` exists: call `manage_roadmap update` to set feature done. Skip if not found.
5. Write final `writeSessionSummary()`. Set `currentState: "DONE"` in autopilot-state.json.
---
## Process
**Prompt the human in plain text** — every phase-continue and human-decision interaction in this skill is plain text only. Do not elevate to `AskUserQuestion`: natural headers like "Continue phase" exceed its 12-char cap, rendering the call as ERR.
1. **INIT** — Resolve spec, derive session slug, check for existing state, parse phases.
2. **ASSESS** — Route by complexity: low/medium auto-plans, high pauses for interactive planning.
3. **PLAN → APPROVE** — Dispatch harness-planner, check approval signals, auto-approve or pause.
4. **EXECUTE** — Dispatch harness-task-executor with plan path, handle checkpoints and retries (max 3).
5. **VERIFY** — Dispatch harness-verifier, confirm code correctness and wiring.
6. **INTEGRATE** — Resolve integration tier, dispatch harness-integration, verify system wiring, knowledge materialization, and documentation per tier.
7. **REVIEW** — Dispatch harness-code-reviewer plus the effective `after:REVIEW` hooks (`resolveReviewHooksWithCanary`: project `skillHooks` plus canary's INSTALLED deterministic detectors when canary is present — forward-wired, not-installed ones skip), fix blocking findings.
8. **PHASE_COMPLETE** — Summarize (including integration report), sync roadmap, loop to ASSESS for next phase or proceed to FINAL_REVIEW.
9. **FINAL_REVIEW → OUTCOME_EVAL → DONE** — Cross-phase review, then the blocking spec-satisfaction gate (halt on a high-confidence NOT_SATISFIED), offer PR creation, write final handoff.
### Context-Budget Trip Wire
Autopilot keeps context fresh **between** phases — every state dispatches a distinct cold subagent — but a single long-running turn (one `harness-task-executor` grinding through a large phase, or a fleet lane building an item end-to-end) can accrue context creep **within its own turn** and silently degrade into the "dumb zone". A **token-anchored, window-keyed trip wire** watches that intra-turn budget. Use `evaluateContextBudget(usedTokens, window)` from `@harness-engineering/core` as the classifier — it returns `ok | warn | trip`.
- **Two-stage policy.** `warn` ⇒ tell the running agent to **converge** the current unit of work and **flush state to disk** (`state.json` / `handoff.json`). `trip` ⇒ **checkpoint-and-restart**: stop the turn and re-dispatch into a **cold subagent** seeded with the **distilled** state file (summarized, not raw-truncated — a raw tail loses the middle, per _Lost in the Middle_). This mirrors autopilot's between-phase cold dispatch; the trip is the intra-turn complement.
- **Window-keyed anchors** (absolute resident tokens, not a flat percentage — 40% of 1M ≈ 400K is deep in the dumb zone):
| Window class | Soft-warn (converge + flush) | Hard trip (checkpoint-and-restart) |
| ------------------------------ | ---------------------------- | ---------------------------------- |
| `1m` (≥ 900K, `[1m]` variants) | **250K** | **350K** |
| `200k` (≥ 150K, Sonnet/Opus) | **80K** | **100K** |
| `local` (≤ 128K, Qwen / local) | **~30%** (`round(0.30×win)`) | **~37.5%** (`round(0.375×win)`) |
- **Measurement rules.** Classify on **TOTAL RESIDENT tokens** = input + output + tool results (tool output — file reads, CI logs, diffs — is the dominant, fastest-growing contributor). Prefer the model's **real cumulative usage counter** over tokenizer estimation; fall back to a `chars/4` estimate only when usage is not surfaced. Utilization percentages are display-only — the trip fires on the absolute token count.
- **Wiring onto recovery machinery.** A `trip` maps onto the existing EXECUTE retry path: write the recovery commit (`[autopilot][recovery]` prefix) capturing the distilled state, then cold re-dispatch the phase's subagent against that checkpoint rather than pushing the exhausted turn further.
---
## Harness Integration
- **State:** `{sessionDir}/autopilot-state.json` (orchestration) + `{sessionDir}/state.json` (task-level, written by harness-execution).
- **Handoff:** `{sessionDir}/handoff.json` — written by each delegated skill, read by next. Autopilot writes final handoff at DONE.
- **Checkpoint commits:** `commitAtCheckpoint()` after passing checkpoints. Recovery commits use `[autopilot][recovery]` prefix.
- **Scratchpad:** cleared at PHASE_COMPLETE via `clearScratchpad()`. Skipped at `rigorLevel: "fast"`.
---
## Gates
- **No reimplementing delegated skills.** Writing planning/execution/verification/review/integration logic → STOP. Delegate via `subagent_type`.
- **No executing without plan approval.** Every plan passes APPROVE_PLAN. No exceptions.
- **No skipping VERIFY, INTEGRATE, REVIEW, or OUTCOME_EVAL.** Human can override findings; steps cannot be skipped. INTEGRATE may be skipped only via explicit "skip" choice with decision recorded in `decisions[]`.
- **Ship authority for OUTCOME_EVAL is derived in TypeScript, never read from the LLM.** Read `authority` off the verdict; do not recompute or override it. A high-confidence `NOT_SATISFIED` (`authority: "blocking"`) HALTS before DONE — shipping over it requires an explicit `override` recorded in `decisions[]`. Every other verdict is advisory.
- **No infinite retries.** EXECUTE budget: 3 attempts. FINAL_REVIEW: 3 cycles. If exhausted, stop and surface.
- **No modifying state files manually.** If corrupted, start fresh.
## Escalation
- **Spec missing Implementation Order:** Cannot identify phases. Ask user to add phase annotations or provide roadmap.
- **Delegated skill fails to produce output:** Check `{sessionDir}/handoff.json`. Report and ask: retry or stop.
- **User wants to reorder phases mid-run:** Update `phases[]` (mark skipped, adjust `currentPhase`). Do not re-run completed phases.
- **Context limits approaching:** Persist state immediately. "State saved. Re-invoke `/harness:autopilot` to continue."
- **2 consecutive phase failures:** Suggest reviewing spec for systemic issues.
## Rationalizations to Reject
| Rationalization | Reality |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "Low complexity means I can skip APPROVE_PLAN" | Low complexity means auto-approval only when no signals fire. Signals override complexity. |
| "I can inline planning logic instead of dispatching to harness-planner" | Iron Law. Autopilot delegates, never reimplements. No exceptions. |
| "Retry budget exhausted but one more approach might work" | 3-attempt budget prevents compounding failure. Exceeding it without human input is unrecoverable. |
| "Keeping research in conversation is faster than scratchpad" | Scratchpad gated by rigor level. At standard/thorough, >500 words must go to scratchpad. |
| "Plan auto-approved, so I can skip recording the decision" | Every approval—auto or manual—is recorded in `decisions[]`. That array is the audit trail. |
| "`Closes roadmap #123` reads fine, GitHub will figure out the issue" | An intervening word breaks GitHub's closing-keyword parser: `closingIssuesReferences` stays empty and auto-done leaves the row `planned`. Use a bare `Closes #123`. |
## Success Criteria
- All phases in the spec are executed in order with plan → execute → verify → integrate → review per phase
- Every plan approval is recorded in `decisions[]` (auto or manual)
- Retry budget (3 attempts) is enforced — exhausted retries surface to user, never silently continue
- FINAL_REVIEW runs on `startingCommit..HEAD` diff and catches cross-phase coherence issues
- OUTCOME_EVAL runs at the ship boundary on the full `startingCommit..HEAD` change; a high-confidence `NOT_SATISFIED` halts before DONE and its verdict persists as an `execution_outcome` node
- State is persisted to `autopilot-state.json` after every state transition — re-invocation resumes correctly
- `harness validate` passes after every phase
## Examples
**Invocation:** `/harness:autopilot docs/changes/security-scanner/proposal.md`
**INIT:** 3 phases found: Phase 1: Core Scanner (low), Phase 2: Rule Engine (high), Phase 3: CLI Integration (low).
**Phase 1 — ASSESS → PLAN:** harness-planner dispatched. Returns plan: `docs/changes/security-scanner/plans/2026-03-19-core-scanner-plan.md` (8 tasks).
**Phase 1 — APPROVE_PLAN (auto):** All signals false. "Auto-approved Phase 1: Core Scanner | auto | low | no concerns | 8 tasks."
**Phase 1 — EXECUTE:** harness-task-executor dispatched with plan path + session. 8 tasks complete. 2 checkpoint commits.
**Phase 1 — VERIFY:** harness-verifier dispatched. Pass. **REVIEW:** harness-code-reviewer. 0 blocking, 2 notes.
**Phase 1 — PHASE_COMPLETE:** "Phase 1 complete. Next: Phase 2: Rule Engine (high). Continue? → yes"
**Phase 2 — ASSESS:** High complexity. "Run `/harness:planning` interactively, then re-invoke." [User plans interactively. Re-invokes.]
**INIT (resume):** "Resuming from PLAN, phase 2: Rule Engine. Found plan: docs/changes/security-scanner/plans/2026-03-19-rule-engine-plan.md"
**Phase 2 — APPROVE_PLAN (paused):** Complexity: high triggered. "Approve? → yes" **EXECUTE → VERIFY → REVIEW → PHASE_COMPLETE.** 14 tasks, 1 retry.
**Phase 3:** auto-plans and executes. **FINAL_REVIEW:** harness-code-reviewer on `startingCommit..HEAD`. 0 blocking, 1 warning. Passed.
**DONE:** 3 phases, 30 tasks, 1 retry. "Create PR? → yes"
**Retry exhaustion (during any phase):**
```
Task 4 fails → Retry 1/3: obvious fix applied, still fails
→ Retry 2/3: expanded context (related files + learnings), still fails
→ Retry 3/3: full context gather (test output + imports + plan), still fails
Budget exhausted. Recorded in .harness/failures.md.
Fix manually and continue / revise plan / stop?
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!