# Goalkeeper

> Autonomous build/audit/verify loop that runs until a machine-checkable done-contract passes, with deterministic anti-spin stop conditions (no-progress, 3-retry, oscillation, budget) and on-disk human escalation. Use for "keep going until it is actually done" multi-round work that has objective pass/fail checks. Not for one-shot tasks or work without verifiable acceptance criteria.

- **Type:** Skill
- **Install:** `agentstack add skill-mikhail-za-goalkeeper-claude-skill-goalkeeper-claude-skill`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Mikhail-Za](https://agentstack.voostack.com/s/mikhail-za)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Mikhail-Za](https://github.com/Mikhail-Za)
- **Source:** https://github.com/Mikhail-Za/Goalkeeper-Claude-skill

## Install

```sh
agentstack add skill-mikhail-za-goalkeeper-claude-skill-goalkeeper-claude-skill
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Goalkeeper

Goalkeeper drives multi-round work to a **machine-checkable "done"** and stops itself the moment it stops making real progress. You hand it a *done-contract* (a goal plus a list of items, each with an objective pass/fail check), or just a goal and let it plan the contract for you. It loops build to green, refusing to declare victory on anything but an independent external check, and it refuses to spin forever: it has deterministic stop conditions wired into code, not into a model's good intentions.

The defining risk this skill addresses is the opposite of laziness. An eager loop will happily run forever, cheat its own tests, or converge perfectly on the wrong target. Goalkeeper is built to fail *loudly and on disk* instead.

**Fable-mode workflow built in (v2).** Beyond the core build/verify loop, Goalkeeper now plans, critiques, and re-plans:

- **Plan.** Hand it a bare `goal` (no `items[]`) and a PLANNER agent decomposes it into a full contract before anything is built.
- **Self-critique.** When every check is green, an adversarial critic hunts for what the checks missed (green is not the same as good) and can re-open the loop with capped remediation items.
- **Scope checkpoint.** Every few rounds it steps back and asks "is finishing still worth it?".
- **Living re-plan.** A builder that discovers the plan is wrong can request the contract be revised mid-run, capped so the plan cannot grow forever.

**Hardware-in-the-loop hardening.** For slow or non-deterministic checks, Goalkeeper adds: `nondeterministic` checks with a retry `passPolicy` (latch / k-of-n) and tree-hash latching, so an environmental miss is never read as a code failure; per-check **shell / cwd / env pinning** (`pwsh`/`bash`/`cmd`/`sh`), so a check runs under exactly the shell it needs; **pipeline checks** (build → deploy → verify) that short-circuit on a failed build and force a fresh build when a stale artifact cannot be ruled out; and a **contract-identity** resume gate, so a different task pointed at a repo with an in-progress run halts (`contract-mismatch`) rather than resuming the wrong work.

These are described in detail below; the contract is therefore mutable and durable, surviving resume.

**Optional power features (all opt-in, all default-off).** Nine newer capabilities are gated behind flags and change nothing unless you turn them on. With no flags set, Goalkeeper behaves exactly as described above. They are: **best-of-N builders** (`caps.candidates` > 1: build N diverse candidate solutions for a hard item and let the deterministic contract pick the winner, no LLM judge); **step memoization** (`memoize: true`: call-granular crash-resume that skips re-running a builder/verifier call whose inputs are unchanged); a **durable approval token** (`approveToken: true`: resolve an escalation by writing a small file instead of re-invoking with args); a **repo map** (`caps.repoMap`: a cheap, token-bounded file/symbol map written once at setup to ground the planner and builder); a **verified pattern library** (`libraryPath`: bank every verifier-passed solution to a cross-repo store and inject the relevant ones as advisory context into future builds); a **durable physical human-in-the-loop gate** (`humanGate: true` + a check's `humanPrecondition`: suspend at zero compute until a person performs a required real-world action, then resume and verify); a **CI-fix mode** (`fix: { command }`: point Goalkeeper at a red build/test command and it drives that exact command to green); a **human-proven diagnosis** (`diagnosis`: hand in a finding you already proved, and Goalkeeper executes the mechanical fix with re-planning locked instead of re-theorizing); and an **auto-retro** (`retro: true`: on converge or a lesson-worthy halt, append a short "how the loop was driven" lesson to a durable LESSONS.md). Each is detailed in its own section below.

## Architecture: three layers

**1. The brain is code, not a prompt.** The stop logic lives in a fixed Workflow script at `goalkeeper.workflow.js`, invoked via the Workflow tool. Convergence, the 3-retry rule, no-progress detection, oscillation detection, and the budget backstop are all evaluated deterministically in that script. They are not the model's *memory* of how it is supposed to behave, so they cannot be argued away mid-run, forgotten after a `/clear`, or talked around by an over-helpful agent.

**2. The spine is durable on-disk state.** Everything that matters persists under `/.goalkeeper/`, split across **two files** plus a log:

- `plan.json`: **runtime state only**: `status`, the set of `passing` item ids, per-item `attempts` (retry counts), the current `iteration`, `prevGoodHead` (the last known-green git SHA), `fpHistory` (the fingerprint history used for no-progress and oscillation detection), `latched` (the tree-hashes at which a `nondeterministic` check has banked a pass, so a later flaky miss at the same tree does not revert or un-converge the run), and `attemptLog` (the per-item **failure ledger**: each failed attempt's structured "why it failed + what to change", a few most-recent kept per item, re-injected into the next builder attempt). The bookkeeper writes this every round, and the latch and ledger survive resume.
- `contract.json`: the **durable working contract**: `goal`, `items` (each with its `expectedOutput`, `check`, and `dependsOn`), and the two spent budgets `replanCount` and `critiqueRounds`. This is the contract the loop is *actually* building to right now, which can differ from what you first handed in (the planner may have generated it, self-critique may have appended items, a re-plan may have split items). It is written only by the planner/seed/re-plan/self-critique persist steps; the bookkeeper never touches it.
- `worklog.md`: an append-only log of per-round reflections.

Further files appear **only when the matching opt-in feature is on**, and are otherwise absent: `results.json` (the memoization ledger, `memoize: true`), `resolution.json` (a human-written durable-token resolution to consume, `approveToken: true` *or* `humanGate: true`), `repomap.md` (the generated, git-ignored repo map, `caps.repoMap` other than `'off'`), `AWAITING-HUMAN.md` (a planned-pause notice written when a check's `humanPrecondition` suspends the run, `humanGate: true`), and `LESSONS.md` (the auto-retro driving-lessons log, `retro: true`; written to `libraryPath` instead when the pattern library is on). They are documented under "Optional power features". One piece of durable state lives **outside** `/.goalkeeper/`: the **verified pattern library** (`libraryPath`) is a cross-repo store at the path you pass, deliberately not under the target repo so a pattern learned in one repo can seed a build in another.

Splitting runtime state from the working contract is deliberate: the bookkeeper rewrites `plan.json` every round, but the contract must not be at risk of a bookkeeping overwrite, so it lives in its own file written only when it genuinely changes.

A **fresh** invocation reads all of these and continues from exactly where the last one left off. Crucially, the anti-spin counters round-trip too: `attempts`, `fpHistory`, **and** the contract-mutation budgets (`replanCount`, `critiqueRounds`) are all restored, so the 3-retry, no-progress, oscillation, re-plan, and critique caps resume mid-progress rather than resetting to zero on every re-invocation. That is the point: Goalkeeper survives crashes, `/clear`, dropped channels, and machine reboots, because the truth lives on disk and not in a session. Workflow's own resume is merely an optimization on top of this; the on-disk state is authoritative.

If a run that has already made progress finds **no** `contract.json` (lost contract state), it refuses to silently re-seed a possibly-stale caller contract over lost revisions and instead escalates (`contract-lost`): you must re-invoke with `amendContract: true` and the contract, or reset state.

**3. The doorbell is Telegram, best-effort only.** When the loop halts and needs a human, it writes `ESCALATION.md` to disk and *then* tries to ping Telegram. The Telegram ping can fail, be muted, or never arrive. `ESCALATION.md` is the system of record. Never treat "no Telegram message" as "nothing to decide". Check the file.

## How to invoke

Call the Workflow tool with the script path and an `args` object. The full shape:

```js
Workflow({ scriptPath: "${CLAUDE_SKILL_DIR}/goalkeeper.workflow.js", args: {
  mode: "build",                 // build | audit | verify  (build is hardened)
  autonomy: "envelope",          // envelope (default: run unattended, halt only on triggers) | leash (pause each round for approval)
  repo: "/path/to/target-repo",
  statePath: null,               // optional; the durable state dir. Defaults to /.goalkeeper (plan.json + contract.json + worklog.md + ESCALATION.md live here)
  contract: { goal },            // OR { goal, items[] }. Goal alone -> the planner decomposes it into items. See templates/contract.schema.json + contract.example.json
  contractPath: null,            // optional: absolute path to a JSON file { goal, items[] }. Read at init, bypassing the args channel. Use for LARGE explicit contracts (inline args can truncate).
  checkPaths: ["tests/**"],      // write-protected; if the builder edits these, the round is auto-reverted
  caps: {
    maxIterations: 20, maxItemRetries: 3, maxStalls: 3, maxTokens: null,
    maxReplans: 2,               // living re-plan: max contract revisions before escalating (replan-budget)
    maxCritiqueRounds: 1,        // self-critique: max critique passes before converging anyway
    scopeCheckEvery: 4,          // scope checkpoint cadence in rounds; 0 disables
    // ---- best-of-N builders (opt-in; default 1 = single builder, unchanged) ----
    candidates: 1,               // N diverse candidate solutions per hard item; the contract (not an LLM judge) picks the winner. Default 1 = off
    candidatesHardOnly: true,    // true: the FIRST try of an item is a single builder; only retries fan out to N (pay N-cost only where the loop struggles)
    maxCandidates: 6,            // hard ceiling on N regardless of per-item override
    maxPlateau: 8,               // halt with reason `plateau` if passing-count does not increase for this many rounds (best-of-N backstop)
    // ---- repo map (opt-in; default 'off') ----
    repoMap: "off",              // 'off' | 'tree' (ranked file tree) | 'symbols' (ctags->git-grep->tree fallback, with top-level symbols)
    repoMapTokens: 1500,         // token budget for the generated .goalkeeper/repomap.md
    repoMapRefreshEvery: 0       // refresh the map every N rounds (0 = only at setup + after a re-plan/self-critique)
  },
  memoize: false,                // opt-in: call-granular crash-resume. Builder/verifier calls keyed by an input fingerprint; a completed call returns its stored result from .goalkeeper/results.json instead of re-running
  approveToken: false,           // opt-in: resolve an escalation by writing .goalkeeper/resolution.json {token, action} instead of re-invoking with resume args
  libraryPath: null,             // opt-in: absolute path to a cross-repo verified-pattern library. Every verifier-passed item is banked there, and patterns relevant to a new item are injected into the builder as advisory context. null = off
  humanGate: false,              // opt-in: honor a check's humanPrecondition. The run SUSPENDS at zero compute (status awaiting-human, costs no retry/iteration/no-progress) until a person performs the action and writes resolution.json {token, action:"ready"}, then resumes and verifies
  fix: null,                     // opt-in: CI-fix mode. { command, shell?, cwd?, env?, goal? } -> synthesize a one-item contract whose check is your red command and drive it to green (the captured red output seeds the first build). null = off
  diagnosis: null,               // opt-in: a HUMAN-PROVEN diagnosis (string, or { text, itemId } to scope it to one item). Injected into every matching builder as a trust-this block; forces maxReplans=0 and single-builder for that item (execute the fix, never re-theorize). null = off
  retro: null,                   // opt-in: true -> on converge or a lesson-worthy halt, a low-effort agent appends a dated "how the loop was driven" lesson to LESSONS.md (at libraryPath when set, else the state dir). Skips uneventful runs.
  denylist: ["git push","deploy","secrets","external-send"],  // forbidden actions, enforced regardless of mode
  telegram: { chatId: null },    // best-effort doorbell
  approvals: [],                 // leash: e.g. ["start"] to clear the pre-build gate; ["contract-gaps"] to proceed past blocking spec gaps
  runRounds: 1,                  // leash: rounds to run per invocation before pausing

  // ---- resume / amend (used when re-invoking after an escalation) ----
  amendContract: false,          // true: OVERRIDE the persisted contract.json with this call's contract.items[] (otherwise the persisted contract wins on resume)
  resetAttempts: [],             // e.g. ["over-limit-returns-429"]: clear that item's retry count + stall (and its attemptLog failure ledger) on a human-amended resume so a relaxed check gets a fresh budget
  freshStart: false              // true: archive ANY existing goalkeeper state (incl. an unfinished run) and start the new task on a clean slate. A converged run auto-archives on new work without this.
}})
```

### The contract is the product

`contract` carries the entire definition of "done": a top-level `goal` string and an `items[]` array. **You can pass `{ goal }` alone** and let the PLANNER agent decompose it into items, **or pass `{ goal, items[] }`** with the items written yourself. Each item has an `id`, an optional `priority`, a `description`, an optional `expectedOutput` (the concrete result that proves the item done), an optional `dependsOn` (ids that must pass first), and an objective, machine-runnable `check`. See `templates/contract.schema.json` for the shape and `templates/contract.example.json` for a worked example.

**Three ways to supply the contract.** There are three ways to hand Goalkeeper the contract: (a) **goal only** (`contract: { goal }`), where the planner builds the `items[]` for you; (b) **inline** (`contract: { goal, items[] }`), where you write the items into the call itself; and (c) **`contractPath`**, an absolute path to a JSON file holding the `{ goal, items[] }` object, read at init and never passed through the args channel. The file path is the robust choice for large or fully-explicit contracts, because a big inline contract can be truncated by the workflow arg channel, while a file cannot. The precedence for the working contract is: a persisted contract (on resume, in `.goalkeeper/contract.json`) wins, then the `contractPath` file, then inline `contract.items`, then the planner (goal only). One interaction to note: with `amendContract: true`, a `contractPath` file (if provided) supersedes inline `contract.items`, so prefer the file when amending a large contract too.

**Item fields that shape scheduling:**

- `expectedOutput`: the concrete artifact or observable result that proves the item is done. It is what the builder aims to produce; `check` is how the loop measures it.
- `dependsOn`: an array of item ids that must be passing before this item is eligible. This enables **dependency-ordered scheduling**: the loop only ever picks an item whose dependencies are all green. (Note: dependency-*aware* scheduling is implemented, but **item** execution is still **sequential**, one item per round, in a dependency-respecting order, not in parallel. The opt-in best-of-N builder does run several candidate builders **for a single item**, but they too run **sequentially on the live repo** (one resets to last-good, builds, is verified, then the next), not in parallel worktrees. *Concurrent* execution of **different items** still needs true parallel gi

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Mikhail-Za](https://github.com/Mikhail-Za)
- **Source:** [Mikhail-Za/Goalkeeper-Claude-skill](https://github.com/Mikhail-Za/Goalkeeper-Claude-skill)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-mikhail-za-goalkeeper-claude-skill-goalkeeper-claude-skill
- Seller: https://agentstack.voostack.com/s/mikhail-za
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
