agentsclimarketplace

Autopilot

Skill Kanevry/session-orchestrator/skills/autopilot

Loop engineering for AI coding agents — turn ad-hoc sessions into a repeatable research → plan → wave-execute → close loop with verification gates. Runs on Claude Code, Codex CLI, Cursor, and Pi. MIT community plugin.

Install
npx -y skills add Kanevry/session-orchestrator --skill autopilot

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Use this skill when running an autonomous session-orchestration loop. Chains session-start → session-plan → wave-executor → session-end for N iterations with all 10 kill-switches (SPIRAL, FAILED wave, carryover > 50%, max-hours, max-sessions, resource-overload, token-budget, stall-timeout, sub-threshold confidence, user-abort). Reads Mode-Selector output (Phase B) to decide auto-execute vs. fallback. Writes one autopilot.jsonl record per loop run. Phase C scaffold (issue #277); implementation lives in scripts/lib/autopilot.mjs (Phase C-1 follow-up).

SKILL.md

23.9 KB, as published. Nobody here has run it

Autopilot Skill

Phase 0.5: Parallel-Aware Preamble

Skip silently when persistence: false in Session Config.

Before any Phase 1 work, run the parallel-aware preamble per skills/_shared/parallel-aware-preamble.md. The preamble detects other active sessions in the worktree-family via findPeers(repoRoot, { mySessionId }), classifies the caller's mode via classifyMode(callerMode) against the exclusivity-matrix, and either:

  • Returns PASS_THROUGH (no other session / always-ok mode) → continue to Phase 1
  • Returns EXCLUSIVE_BLOCKED → fires Exclusive-Conflict AUQ from skills/_shared/parallel-aware-auq.md
  • Returns PROMOTION_OFFER → fires Worktree-Promotion AUQ (via enterWorktree() from scripts/lib/autopilot/worktree-pipeline.mjs — see parallel-aware-auq.md outcome-handling)

On any non-PASS_THROUGH outcome that does not result in immediate exit, append a Deviation to STATE.md via appendDeviationOnDisk(repoRoot, isoTimestamp, message) from scripts/lib/state-md.mjs.

Implementation reference: skills/_shared/parallel-aware-preamble.md § Implementation. AUQ reference: skills/_shared/parallel-aware-auq.md.

Status

Phase C-1.b complete (2026-04-25, issues #295 + #300). Runtime at scripts/lib/autopilot/kill-switches.mjs:18-32 (the frozen KILL_SWITCHES enum is SSOT) enforces all 10 kill-switches:

  • Pre-iteration (6, #295 + #355): max-sessions-reached, max-hours-exceeded, resource-overload, low-confidence-fallback (with iter-1-fallback / iter-2+-exit asymmetry), user-abort, token-budget-exceeded (cumulative tokens ≥ --max-tokens).
  • Post-iteration (1, ADR-364 §3): stall-timeout — no progress marker in autopilot.jsonl within the threshold (default 600s; missing file → no kill).
  • Post-session (3, #300): spiral, failed-wave, carryover-too-high. Read schema-canonical fields off the sessionRunner return shape: agent_summary.{spiral, failed} (numeric counts) and effectiveness.{carryover, planned_issues}. Absent fields → no kill (forward-compatible: a sessionRunner that does not yet emit those fields silently no-ops the post-session gates).

Atomic autopilot.jsonl writer (tmp+rename, schema_version 1) and silent-clamp parseFlags shipped in C-1. autopilot_run_id is passed into sessionRunner via args.autopilotRunId; production callers MUST persist it into the per-iteration sessions.jsonl record (additive optional field, schema_version 1 compatible). See skills/wave-executor/SKILL.md § Return Shape Contract and skills/session-end/SKILL.md § Phase 3.7.

Purpose

Autopilot collapses the per-session attention cost when Mode-Selector is confident enough to make routine decisions autonomously. A productive day commonly ships 3–7 sessions; each manual session-start costs the user 10–60 seconds of context-switch attention. When the session is genuinely routine (mechanical refactor, post-merge housekeeping, repeated follow-ups from a planned epic), that attention cost is pure overhead.

/autopilot reads the Mode-Selector recommendation, executes the recommended session if confidence clears the threshold, then loops — checking kill-switches between iterations. The user invokes the loop once and walks away; autopilot stops itself when work runs out or quality degrades.

This is opt-in by design: autopilot never starts itself. The user must run /autopilot explicitly. Configuration thresholds (--max-sessions, --max-hours, --confidence-threshold) are CLI flags, not Session Config defaults — the user signals intent for THIS run, not a standing policy.

Command Surface

/autopilot [--max-sessions=N] [--max-hours=H] [--confidence-threshold=0.X] [--dry-run]
FlagDefaultBoundsMeaning
--max-sessions51..50Iteration cap (graceful exit when reached)
--max-hours4.00.5..24.0Wall-clock budget for entire loop
--confidence-threshold0.850.0..1.0Minimum selectMode confidence for auto-execute
--dry-runfalsePrint planned iterations without executing

Out-of-range values silently clamp to bounds. --dry-run exits after printing — never invokes session lifecycle.

Loop Semantics

state := { iterations_completed: 0, started_at: now(), kill_switch: null, sessions: [] }

WHILE state.iterations_completed < max-sessions:
  # Pre-iteration kill-switches (6)
  IF aborted: kill_switch := 'user-abort'; break
  IF state.iterations_completed >= max-sessions:
    kill_switch := 'max-sessions-reached'; break
  IF (now() - state.started_at) > max-hours:
    kill_switch := 'max-hours-exceeded'; break
  IF cumulative_tokens_used >= max-tokens:
    kill_switch := 'token-budget-exceeded'; break
  IF resource_verdict() == 'critical' AND peer_count() > autopilot-peer-abort:
    kill_switch := 'resource-overload'; break

  recommendation := mode-selector.selectMode(<live signals from session-start Phase 7.5>)

  IF recommendation.confidence < confidence-threshold:
    IF state.iterations_completed == 0:
      fallback_to_manual()  # iteration 1: hand off cleanly to manual /session flow
    ELSE:
      kill_switch := 'low-confidence-fallback'  # iteration 2+: exit, let user decide
    break

  cap := resource_adaptive_cap()
  session_result := run_session(mode=recommendation.mode, agents_per_wave_cap=cap)
  state.sessions.append(session_result.session_id)

  # Post-iteration kill-switch (1)
  IF stalled(autopilot.jsonl) >= stall-timeout: kill_switch := 'stall-timeout'; break
  # Post-session kill-switches (3)
  IF session_result.spiral_detected: kill_switch := 'spiral'; break
  IF session_result.failed_waves > 0: kill_switch := 'failed-wave'; break
  IF session_result.carryover_ratio > 0.50: kill_switch := 'carryover-too-high'; break

  state.iterations_completed += 1

write_autopilot_jsonl(state, kill_switch)
print_summary(state, kill_switch)

Atomicity rule: iteration boundaries are atomic. A session must complete (/close including the post-session writes) before the next iteration starts. Autopilot does NOT abort sessions mid-flight; kill-switches are checked AFTER each session completes.

Kill-Switches

All 10 kill-switches, grouped by check phase (mirrors the KILL_SWITCHES enum in scripts/lib/autopilot/kill-switches.mjs:18-32):

Kill-switchPhaseTriggerRecovery hint
max-sessions-reachedpre-iterationiterations_completed >= --max-sessionsGraceful — not an error.
max-hours-exceededpre-iterationWall-clock exceeds --max-hoursRe-run with higher --max-hours or address slow waves.
resource-overloadpre-iterationverdict==critical AND peers > autopilot-peer-abortWait for peer sessions to complete or close them.
low-confidence-fallbackpre-iterationconfidence < threshold (iteration 2+)Re-run with lower --confidence-threshold or run next session manually.
user-abortpre-iterationCtrl+C / Esc (AbortSignal)Re-run when ready.
token-budget-exceededpre-iterationcumulative_tokens >= --max-tokens (#355)Re-run with a higher --max-tokens budget or split the work.
stall-timeoutpost-iterationNo progress marker in autopilot.jsonl within threshold (ADR-364 §3; default 600s)Inspect the stalled iteration; missing telemetry file is NOT a kill.
spiralpost-sessionwave-executor spiral detection fires (agent_summary.spiral > 0)Triage the spiraling wave manually; autopilot will not retry.
failed-wavepost-sessionAny wave reports agent_summary.failed > 0Investigate failure mode (test contract drift, env issue). Re-run after fix.
carryover-too-highpost-sessioncarryover/planned > 0.50Last session under-delivered. Reduce scope or split issues before resuming.

Resource-Adaptive Concurrency

Autopilot does NOT hard-block on peer Claude processes. It adapts agents-per-wave cap per iteration based on the most-restrictive resource signal.

TierRAM freeSwapPeersmacOS memory_pressurecap
green≥ 6 GB< 1 GB≤ 2≥ 30% freeSession Config default
warn4–6 GB1–2 GB3–415–30% free4
degraded2–4 GB2–3 GB5–65–15% free2
critical< 2 GB> 3 GB> 6< 5% free0 (coord-direct)

Most-restrictive-signal-wins: [ram=8GB, swap=0, peers=7] → critical (peer rule wins).

Defaults are conservative initial estimates. Phase C-3 follow-up calibrates the swap and memory_pressure thresholds against real autopilot-run effectiveness data.

Production Wiring

Phase C-1 ships runLoop as a pure controller. Phase C-1.c ships buildLiveSignals as the canonical signals-assembly helper. This section documents the in-process driver protocol (Option B from #301): how Claude — running as the coordinator in a chat session — drives runLoop between manual /session invocations. The headless wrapper (Option A, scripts/autopilot.mjs CLI spawning claude -p) is reserved for Phase C-5.

Dependency-Injection Contract

runLoop requires four injected dependencies:

FieldSignatureSource
modeSelector() => Promise<{mode, confidence, rationale?}>wraps selectMode(await buildLiveSignals())
sessionRunner({mode, autopilotRunId}) => Promise<{session_id, agent_summary?, effectiveness?}>wraps a /session <mode> invocation; reads sessions.jsonl tail to construct return value
resourceEvaluator() => {verdict}wraps evaluate(await probe(), thresholds) from resource-probe.mjs
peerCounter() => numberreads claude_processes_count from a fresh probe() snapshot

abortSignal is optional (Ctrl+C / Esc → user-abort kill-switch).

In-Process Driver Skeleton

import { runLoop, parseFlags } from '$PLUGIN_ROOT/scripts/lib/autopilot.mjs';
import { buildLiveSignals } from '$PLUGIN_ROOT/scripts/lib/build-live-signals.mjs';
import { selectMode } from '$PLUGIN_ROOT/scripts/lib/mode-selector.mjs';
import { probe, evaluate } from '$PLUGIN_ROOT/scripts/lib/resource-probe.mjs';

const flags = parseFlags(process.argv.slice(2));

const modeSelector = async () => {
  // Each iteration rebuilds signals from current disk state. STATE.md will be
  // freshly idle-reset by the previous /close, sessions.jsonl will have the
  // new tail entry, etc. This is the contract: live signals every iteration.
  const signals = await buildLiveSignals({ backlogLimit: 50 });
  return selectMode(signals);
};

const resourceEvaluator = () => {
  const snapshot = probeSync();   // or cached snapshot if probe is async
  return evaluate(snapshot, thresholds);
};

const peerCounter = () => {
  // Synchronous-friendly count from a recent probe snapshot.
  return latestSnapshot.claude_processes_count ?? 0;
};

const sessionRunner = async ({ mode, autopilotRunId }) => {
  // The coordinator (Claude) invokes /session <mode> manually here. After the
  // session completes (/close runs, sessions.jsonl appended), this function
  // reads the tail entry and projects it into the runLoop return-shape.
  const tail = readSessionsJsonlTail(1);   // last line, normalized
  return {
    session_id: tail.session_id,
    agent_summary: tail.agent_summary,    // {complete, partial, failed, spiral}
    effectiveness: tail.effectiveness,    // {planned_issues, carryover, completion_rate, ...}
  };
};

const result = await runLoop({
  ...flags,
  modeSelector,
  sessionRunner,
  resourceEvaluator,
  peerCounter,
});

Why In-Process First

The in-process driver has Claude (the coordinator) call /session <mode> between runLoop iterations, with runLoop orchestrating the kill-switches. Trade-offs:

  • Pro: zero new infra. Reuses canonical kill-switch logic. Validates buildLiveSignals against real Phase 7.5 swap before headless complexity. Each iteration carries inter-session memory through STATE.md / sessions.jsonl / learnings.
  • Con: not truly autonomous — Claude must stay in the chat. Doesn't deliver walk-away UX. That's Phase C-5's job.

autopilot_run_id Propagation

When runLoop invokes sessionRunner({mode, autopilotRunId}), the per-iteration sessions.jsonl record MUST carry autopilot_run_id: <id>. session-end Phase 3.7 writes this field. Manual sessions write null or omit it — readers treat both identically per the v1 schema additive convention. See skills/session-end/session-metrics-write.md.

Acceptance Signals

A live /autopilot invocation against this wiring produces a non-zero confidence recommendation when at least one signal source is populated (state-md rec fields, sessions.jsonl tail, learnings, or backlog). Confidence at 0.0 with all four sources populated is a Mode-Selector heuristic bug (file as [Mode-Selector v1.x quirk] issue), not an autopilot bug.

Pre-Loop Verdict Gate (dispatcher → autopilot handoff — #682)

When the cross-repo dispatcher (skills/dispatcher/SKILL.md) routes into an autopilot launch, a pre-loop suitability verdict decides whether the launch may proceed WITHOUT per-selection operator confirmation. This is distinct from — and runs BEFORE — the loop's 10 kill-switches:

  • The verdict gate is a PRE-LAUNCH decision. It is computed ONCE, at the dispatcher → runLoop handoff, before the first iteration starts. It answers "may I launch this repo autonomously, or must I ask first?" — NOT "should I stop the running loop?"
  • The 10 kill-switches are PER-ITERATION and reused UNCHANGED. Once runLoop starts, the frozen KILL_SWITCHES enum (scripts/lib/autopilot/kill-switches.mjs:18-32) governs when the loop stops, exactly as documented above. The verdict gate adds NO new kill-switch, modifies NONE of the existing 10, and does not re-implement any of them.
  • The gate engine is computeSuitabilityVerdict(deps) from scripts/lib/autonomy/suitability.mjs — a pure four-gate AND (confidence ≥ floor; kill-switch fired-rate < 0.2 over the recent runs, omitted below 5 runs; CI ≠ red; resource ≠ critical). The dispatcher gathers every signal and passes it in (DI); the engine reads no files.
  • Kill-switch history feeds G2 via readRecentAutopilotRuns from scripts/lib/autopilot/recent-runs.mjs, which reads THIS repo's .orchestrator/metrics/autopilot.jsonl (newest-last, never throws). The verdict's G2 gate counts those records and reads each one's persisted kill_switch field — it never re-enumerates or re-derives the switches; it reads the history the loop already wrote.
  • FAIL-CLOSED launch wiring: the dispatcher launches without confirmation ONLY when autonomy === 'autonomous-gated' AND verdict.suitable === true. Every other case (any non-autonomous-gated dial, a CI-red / resource-critical / low-confidence verdict) informs the operator and asks before launch. resolveDispatcherAutonomy defaults to 'off' when unconfigured, so an absent config forces inform + ask. See skills/dispatcher/SKILL.md § Phase 1.5 for the full sourcing table and invariant.
  • null signals are honest, not failures (NICE-b). On a CI-fetch or resource-probe failure the dispatcher passes ci = null / resourceVerdict = null (not a synthesized { status: undefined } or a fabricated 'green'). Each null ⇒ the gate passes + warns — it surfaces a warning the operator sees, it does not block on its own.
  • forcedFail is reachable end-to-end (NICE-c). When CI is red OR resource is critical, verdict.suitable === false REGARDLESS of confidence (the engine words the rationale FORCED: CI red / FORCED: resource critical), so even under autonomous-gated the launch falls to inform + ask. Reachability depends on the dispatcher wiring the live signals through (CI wrapped as { status }, the real resource verdict string) rather than masking them — see skills/dispatcher/SKILL.md § Phase 1.5.

This gate does NOT change the loop. Autopilot remains opt-in, and the kill-switch contract is unchanged — the verdict gate only governs HOW the loop is entered (auto vs. confirm).

Telemetry

One record per /autopilot invocation, written to .orchestrator/metrics/autopilot.jsonl via atomic tmp + rename. See "Autopilot Loop" (#277; archived in the private Meta-Vault) § Output for the full schema.

Each iteration's sessions.jsonl entry gets an additional optional field autopilot_run_id (string or null) so retros can join across the two files without schema changes.

Manual sessions write autopilot_run_id: null (or omit the field — both treated identically by readers per the v1 schema additive convention).

Integration with Other Skills

  • mode-selector.mjs::selectMode — sole source of mode + confidence per iteration. Autopilot does not implement its own mode logic; v1.x quirks affect autopilot exactly as they affect manual session-start.
  • resource-probe.mjs::probe + evaluate — extended in Phase C-2 with swap and memory_pressure signals. Existing consumers (manual session-start, wave-executor) benefit from the new signals automatically.
  • session-start / session-plan / wave-executor / session-end — invoked unmodified. Autopilot is a controller around the existing session lifecycle, not a replacement.
  • session-registry.mjs — peer-count signal source. Autopilot reads but does not write to the registry beyond the standard hook.
  • mode-selector-accuracy — autopilot iterations write accuracy learnings exactly like manual sessions (Phase B-4 contract). The chosen field reflects autopilot's auto-execute decision, which equals recommendation.mode when confidence ≥ threshold.

Critical Rules

  • Never auto-merge or auto-push beyond /close defaults. /close already pushes to origin; autopilot does not add PR creation, merge, or force-push behavior.
  • Iteration boundaries are atomic. Never abort a running session to start a new one.
  • Kill-switches checked AFTER each session. Even if a kill-switch will fire after iteration N, iteration N completes cleanly first.
  • Iteration 1 sub-threshold falls back to manual; iteration 2+ exits with kill-switch. This asymmetry is intentional — see PRD Q8.
  • autopilot.jsonl is the SOLE writer's responsibility of autopilot.mjs. Other skills must not append to or rewrite this file.
  • Mode-Selector contract is read-only here. Autopilot does not modify selectMode output, does not re-rank alternatives, does not patch confidence values.

Anti-Patterns

  • Do not invoke /autopilot from inside a running session. The skill is a top-level command; nested invocation is undefined behavior.
  • Do not modify autopilot.jsonl schema additively without bumping schema_version. Readers MUST treat unknown fields as a forward-compat signal, not as corruption.
  • Do not bypass selectMode to force-run a specific mode. If you want to run a specific mode, use /session [mode] manually — that is autopilot's fallback path.
  • Do not lower --confidence-threshold below 0.5 in production. The Mode-Selector fallback table treats < 0.5 as suggestion-only; autopilot at that threshold becomes a random-walk over modes.
  • Do not implement kill-switch logic in this skill. Kill-switch enforcement is in scripts/lib/autopilot.mjs. The skill documents the contract; the runtime enforces it.

Configuration

The autopilot block in Session Config (CLAUDE.md / AGENTS.md) accepts the following fields. All fields are optional; omitting a field applies the documented default.

autopilot:
  bg-isolation: worktree   # worktree | none (default: worktree) — see #431

bg-isolation

Type: worktree | noneDefault: worktree

Controls whether autopilot --multi-story creates a per-story git worktree before spawning sub-sessions.

worktree (default): Each story pipeline receives its own isolated git worktree via EnterWorktree. Parallel writes are safe because every agent edits a private working copy. Cost: disk space proportional to the number of concurrent stories plus the latency of worktree creation at story-start.

none (opt-in): No worktrees are created. Sub-sessions spawn directly in the main working tree. Useful for monorepos where worktree creation is impractical due to large node_modules, sparse-checkout setups, or build caches that must be shared. Requires file-scope discipline: when max-stories > 1, every story must edit a disjoint set of files. If two stories touch the same file simultaneously, edits will collide silently. To enforce acknowledgement of this discipline, autopilot-multi requires --deconflict-paths=<glob> whenever bg-isolation: none AND max-stories > 1; omitting the flag is a hard error (exit 1). See .claude/rules/parallel-sessions.md PSA-001/002/003.

Operator-awareness note: CC 2.1.133 silently flipped worktree.baseRef default from head to origin/<default>, breaking users who relied on unpushed commits being included in their worktree base. The same class of upstream change can affect bg-isolation semantics in a future CC release. Treat CC changelog entries related to worktree or --bg session behaviour as requiring a re-read of this section before upgrading.

References

  • PRD: "Autopilot Loop" (#277; archived in the private Meta-Vault)
  • Implementation (Phase C-1 + C-1.b): scripts/lib/autopilot.mjs — exports runLoop, parseFlags, writeAutopilotJsonl, KILL_SWITCHES, FLAG_BOUNDS, SCHEMA_VERSION, DEFAULT_PEER_ABORT_THRESHOLD, DEFAULT_JSONL_PATH, DEFAULT_CARRYOVER_THRESHOLD
  • Tests (Phase C-1 + C-1.b): tests/lib/autopilot.test.mjs
  • Command file: commands/autopilot.md
  • Mode-Selector contract: skills/mode-selector/SKILL.md
  • Resource probe: scripts/lib/resource-probe.mjs
  • Session registry: scripts/lib/session-registry.mjs
  • Wave-executor return shape: skills/wave-executor/SKILL.md § Return Shape Contract
  • Sessions.jsonl writer: skills/session-end/session-metrics-write.md
  • Pre-loop verdict gate (#682): scripts/lib/autonomy/suitability.mjs (computeSuitabilityVerdict) · scripts/lib/config/dispatcher-autonomy.mjs (resolveDispatcherAutonomy) · scripts/lib/autopilot/recent-runs.mjs (readRecentAutopilotRuns) · skills/dispatcher/SKILL.md § Phase 1.5
  • Epic: #271 v3.2 Autopilot — Autonomous Session Orchestration
  • Issues: #277 Phase C scaffold, #295 Phase C-1 runtime, #300 Phase C-1.b follow-up
  • Phase A PRD: "STATE.md Recommendations Contract" (#271; archived in the private Meta-Vault)
  • Phase B PRD: "Mode Selector" (#276; archived in the private Meta-Vault)

Open Questions (Phase C-1 to resolve)

  • --confidence-threshold=auto — let autopilot self-tune from accumulated mode-selector-accuracy learnings? Requires ≥ 20 accuracy learnings before useful.
  • STATE.md autopilot-active: true field — should other Claude sessions detect via the session-registry and refuse to start during an autopilot run? Dogfooding will inform.
  • failed-wave granularity — distinguish "agent failed but was retried successfully" from "wave ended with un-recovered failures"? Requires wave-executor schema audit.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.