# Afk Pipeline

> Operate the AFK pipeline for a registry-confirmed repository. Use when the user explicitly asks for AFK work, Pipeline Flags, an Agent Brief, pipeline dispatch, or pipeline installation, or when the active workspace contract routes substantial eligible development through AFK.

- Skill: `jorgemenadev/afk-pipeline` (Agent Skill, multi-file: 40 files)
- Install (CLI): `npx skillmds@latest add jorgemenadev/afk-pipeline`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jorgemenadev/afk-pipeline/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: jorgemenadev (https://skillmd.com/u/jorgemenadev)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/jorgemenadev/afk-pipeline

---


# AFK Pipeline

Substantial eligible development routed to **AFK** uses a GitHub-issue label to trigger a pipeline that implements the change, verifies it in a real browser, opens a **draft PR** carrying committed QA Evidence, and — only when the requester asked for one — publishes a visual recap. A human reviews and merges. In a registered repo on the AFK lane you never implement product code inline — your deliverable is the **brief, and the brief is the contract**: the pipeline agents get no follow-up questions.

## Preamble (run FIRST)

```bash
cd "$(git rev-parse --show-toplevel)" 2>/dev/null
_R=.agents/afk-pipeline/REGISTRY.md
[ -f "$_R" ] && echo "REGISTRY: $_R" || echo "REGISTRY: missing"
```

- `REGISTRY: missing` → either you are not in the consumer's home repo (ask where the registry lives), or the pipeline was never adopted here (offer [reference/installation.md](reference/installation.md)).
- Otherwise Read the registry file in full now — it maps each repo to trigger labels, default lane, clone path, and quirks. Do not route from memory of it.

## Contract

- Every substantial eligible development task routed to AFK in a registered repo goes through an Agent Brief — no inline implementation, no direct pushes, no merges.
- Phases are fail-safe ON: Pipeline Flags only reduce work; absent flags mean the full pipeline (implement → advisory second-model review → verify → draft PR → recap) on `engine: claude`. **Recap is opt-in at authoring time** (v2.9.3): every brief stamps `recap: off` unless the requester explicitly asked for a recap — the runtime fail-safe stays `recap: on`, so the line is mandatory, never omitted.
- **Implement↔verify retry loop (v2.13.0):** on a behavioral verify fail the wrapper re-enters implement with a bounded feedback envelope, up to `retries:` extra attempts (default **2**, allowed 0–3, values >3 clamp to 3). A clean first pass surfaces nothing; a run that passes after a retry adds a Convergence section on the PR and one completion-comment clause; budget/deadline/indeterminate terminals get a `Stopped:` blocked comment with per-attempt rows. **Behavior change:** default `retries: 2` applies after consumer regen — stamp `retries: 0` to restore single-attempt. Under `verify: off` retries are inert (one attempt). **Proof-only:** `RETRY_PROOF_VERDICTS` (JSON map of attempt → verdict override, incl. indeterminate) may be set as a repo Actions variable for seeded Tier-1 proofs; the generated workflow **never** sets it — remove the variable after the proof.
- Recorded browser evidence is **experimental and opt-in** (v2.10.0): every new brief stamps `recording: off` unless the requester explicitly asks for video proof. `recording: on` is v1-hosted-only (cloud or Vercel Sandbox), requires verify, uploads the full WebM as a 30-day Actions artifact, and commits only a small GIF preview to the PR. Before recommending it, read [reference/experimental-recording.md](reference/experimental-recording.md).
- The implementation engine is a brief-stamped contract (v2.9.0): `engine: claude` by default; `engine: codex` runs the four agent phases (implement / review-fix / verify / write-pr) on Codex when the dispatcher has confirmed the target lane can run Codex; `engine: cursor` runs **implement only** on the Cursor Agent CLI, then keeps review-fix / verify / write-pr on Claude because Cursor sessions are non-resumable and structured-output retry needs resume. Cursor v1 is cloud-lane only (`agent:implement`), requires the repo secret `CURSOR_API_KEY`, defaults to model `grok-4.5-xhigh`, and honors a `CURSOR_MODEL` env override. `engine: grok` (v2.12.0, implemented) runs **implement only** on the Grok Build CLI (native xAI; custom sandcastle provider at `.sandcastle/grok/provider.ts`, model `grok-4.6` via `GROK_MODEL` var, effort `high` via `GROK_EFFORT` — the model default), then keeps review-fix / verify / write-pr on Claude; Grok headless sessions ARE resumable (`-s`/`-r`, proven 2026-07-12), so codex-parity (all four phases) is a permitted later expansion, but v1 mirrors Cursor's smaller runner surface. Grok v1 is cloud-lane only (`agent:implement`): auth is the `GROK_AUTH_B64` repo secret — a base64 of the dispatch host's `~/.grok/auth.json` (X Premium+ OAuth), re-cut at dispatch like `CODEX_AUTH_B64` — materialized to the runner and bind-mounted into the per-phase container; the image installs the grok CLI only for grok-stamped runs (`INSTALL_GROK` build-arg). The pre-implement auth smoke is a real tiny chat call, NOT a models listing (the models endpoint answers without the chat entitlement while chat 403s — observed 2026-07-12). Consumers on older parsers fail-safe an `engine: grok` stamp back to `engine: claude`; regen to v2.12.0 + set `GROK_AUTH_B64` before stamping grok. Recap stays Claude. The second-model review defaults to the opposite vendor for Codex (`engine: codex` + absent `review-engine` resolves to `review-engine: claude`); Cursor and Grok follow the Claude default review contract. Explicit `review-engine` overrides are honored and visible; no phase silently swaps engines when a requested engine is unavailable.
- Repos with a Convex backend (`convexDir` in `.sandcastle/config/pipeline.json`) get a **Convex integrity gate**, always-on and not flag-controlled: the pipeline regenerates `_generated` with real codegen against a keyless anonymous local backend; the schema/types not validating fails the RUN, while divergent committed files are **self-healed** (v2.5.1) — the gate commits the canonical codegen output itself (visible `[convex-gate]` commit in the PR) instead of discarding the whole implement run over machine output (a full andyChat slice was lost to this on 2026-07-07). Hand-editing `_generated` is forbidden everywhere. Companion stack policy: merging a Convex change must trigger an automatic backend release (Vercel-coupled build or a main-push deploy workflow) — a repo where merged backend code waits for a manual `convex deploy` is drift.
- Skip decisions key on **predicted diff shape** — the files and surfaces the change will actually touch — never on how the task is framed.
- Labeling starts a paid, unattended run. Show the complete brief and flags before dispatch, then follow the active workspace authority contract — including any dispatch gate it defines (e.g. a contract that requires the user's explicit go-ahead immediately before any cloud or sandbox dispatch: show brief + flags + lane, then wait for it). Beyond the contract's own gates, do not add extra confirmation rounds.
- **No failed run loses committed work** (v2.4.0): any failure or timeout-cancel after the branch exists salvage-pushes the agent branch (uncommitted debris included as an explicit WIP commit) and the failure comment names it. A salvaged branch is unreviewed-for-merge by definition; a successful retry force-replaces it. The one class this cannot cover: a crash inside the implement phase before the agent commits. **Vercel Sandbox lane limitation (pre-existing, spec §9.3 — not a retry-loop regression):** workspace sync-out runs only after a phase succeeds, so in-microVM commits of the *crashing* phase itself are lost; completed phases' synced commits (including the wrapper's host-side evidence-relocation commits) are always salvageable. Upstream sync-on-failure is the eventual fix (skills#74 territory).
- **Three lanes** (v2.5.0, rearchitected v2.6.0), selected per task by trigger label: `agent:implement` (cloud — hosted VM with the agent in per-phase Docker), `agent:implement-local` (self-hosted runner, agent in per-phase Docker), `agent:implement-sandbox` (hosted runner + **Vercel Sandbox** microVM). The non-local runner is per-repo config (v2.7.0): pipeline.json `cloudRunsOn` = `ubuntu-latest` (GitHub, default) or a Blacksmith tag like `blacksmith-4vcpu-ubuntu-2404` (needs the Blacksmith GitHub App on the repo's org; a tag with no backing runner queues forever — first run after flipping validates). Local-lane containers are CPU-capped (v2.7.0, `dockerCpus`, default 3) so concurrent runs can't starve the runner host; RAM is still the real ceiling — the registry's routing policy (max concurrent local runs, overflow lane) is the primary defense, the cap is the backstop. `engine: codex` uses the dedicated AFK Codex home (`~/.codex-afk`) on the local lane with a host lock, cloud-lane CODEX_AUTH_B64 seed materialization on the runner, and sandbox-lane CODEX_AUTH_B64 delivery via a per-command in-VM seed after sandbox creation. Codex-stamped briefs never overflow to a codex-incapable lane: queue them or re-stamp `engine: claude — override ignored: <reason>` before labeling. `engine: cursor` is cloud-lane only in v1; local and sandbox labels fail fast, while the shared Docker image carries the CLI solely for the hosted cloud lane. The sandbox lane rides sandcastle's own isolated-provider machinery: the AGENT runs inside a per-phase Firecracker microVM and the workspace syncs in/out via git bundle/format-patch, so push, salvage, artifacts, and step outputs stay host-side on all three lanes. `.sandcastle/vercel/provider.ts` is a thin hardened stand-in for sandcastle's stock `sandboxes/vercel` (stock gaps as of 0.12.0: exec ignores `stdin` — which is how agent providers deliver the PROMPT — no transport-fault tolerance, no `persistent: false`; collapse it once upstreamed). Per-repo provisioning: `sandbox: {teamId, projectId}` in pipeline.json (the repo's Vercel team + its `afk-sandbox` project), the team-scoped `VERCEL_SANDBOX_TOKEN` repo secret, and the `@vercel/sandbox` devDependency. Unprovisioned label use fails loudly at the first agent phase.

## The loop

1. **Route.** Find the target repo's registry row. No row → not an AFK repo: do normal work, or offer installation. Done when: row in hand.
2. **Mini-grill.** Interrogate the request until the brief is writable: goal, constraints, acceptance criteria, blast radius, and what could hide inside it. One question at a time, with your recommended answer. Never paper over ambiguity: an unclear or underspecified request means the issue does not get written yet — ask until it's settled. Escalate to a full `grill-with-docs` session (target = this repo) when the task reshapes domain language, carries a trade-off worth an ADR, or introduces a new integration, credential, or external service. Done when: every acceptance criterion is checkable by an agent with no context (browser or shell), and you can predict the diff shape.
3. **Flags.** Apply [reference/phase-rubric.md](reference/phase-rubric.md) to the predicted diff shape. Also read `~/.hermes/profiles/matias/.agents/engine-override.json`: absent, malformed, unsupported lane, carve-out, or incapable target means off and the brief still stamps `engine: claude — override ignored: <reason>`; a valid codex override on a capable lane stamps `engine: codex`; a valid cursor override stamps `engine: cursor` only for the cloud lane with `CURSOR_API_KEY` configured. Codex-stamped briefs never overflow to a codex-incapable lane, and Cursor-stamped briefs never move to docker or sandbox. Done when: each flag has a one-line reason.
4. **Brief.** Write it per [reference/brief-template.md](reference/brief-template.md), `### Pipeline` section included, and show the user the full body plus your flag reasoning. Every new brief stamps `engine:`. Before a cloud Grok dispatch, run `node <skill>/scripts/seed-grok-auth.mjs --repo <owner>/<repo> --canonical <encrypted-auth-path>`; it pins the auth home, proves a real chat, copies the opaque refreshed file to the encrypted store, seeds `GROK_AUTH_B64`, and reports the secret timestamp without printing credentials. Done when: the helper exits 0, then and only then may the label land. When dispatching a cloud/sandbox codex run, refresh the target repo secret from the mini's current auth **before any label lands, as its own command, and confirm it exited 0**: `base64 < "$HOME/.codex-afk/auth.json" | gh secret set CODEX_AUTH_B64 -R <owner>/<repo>` (`gh secret set` reads stdin — it has NO `--body-file` flag; the flag error is silent doom if compounded). Never chain a seed refresh and label: a failed refresh must leave the run undispatched. **Codex quota runbook:** ChatGPT usage limits can exhaust mid-run. Diagnose on the dispatch host with `echo "Reply OK" | codex exec --json -m <model>`; a `turn.failed` usage-limit message is the signature. Wait for reset or re-stamp Claude; never burn retries on a quota-dead account.
5. **Authority.** Record that the user saw the full brief and flags, then apply the active workspace authority contract. Stop only for an unresolved judgment, permanent-loss, or human-only gate, or a dispatch gate the contract defines (e.g. required user confirmation before a cloud or sandbox dispatch); otherwise create and label without an extra approval round. Done when: the routing decision and any gate disposition are durable.
6. **Trigger.** Create the issue, add the repo's trigger label (lane per registry default). Watch by polling `gh run view <id> --json status` in a loop — never `gh run watch` (unbounded output).
7. **Deliver.** When the draft PR opens, hand over PR + QA Evidence + recap links (when a recap was requested), plus the live deploy-preview URL when the repo has a PR-preview integration (e.g. Vercel comments it on the PR) — it's the fastest human review surface, ahead of screenshots. If the diff touches the pipeline itself, say plainly: pipeline changes execute from the default branch, so they can't self-prove — their first validating run is the first run *after* merge.

## Output format

End a triggered task with: issue URL, run URL, flag set with reasons, and a status line — `WAITING_ON: review` or `BLOCKED: <one line of evidence>`.

## Watching a local-lane run

The agent's output streams to the **GitHub Actions job log** (repo → Actions → agent-implement run, or `gh run watch -R <owner>/<repo>`), plus the committed evidence dir and the PR recap. Docker Desktop is only for liveness/CPU/kill: sandcastle boots the container detached with a keepalive PID 1 and drives all work via `docker exec`, so the container's own Logs tab is empty **by design**. Container names are `sandcastle-<uuid>` — hardcoded upstream in `@ai-hero/sandcastle` (no name option; never `docker rename` a live one, sandcastle addresses it by that name). Identify containers by IMAGE: `docker ps --format '{{.Names}}  {{.Image}}  {{.RunningFor}}'` — the per-repo image (`sandcastle-<project>`) tells you whose run it is.

