Set or check status of a durable goalkeeper goal. Use this skill when the user invokes /goal "<objective>" to start a new contract-driven goal, or /goal with no arguments to see status of the currently active goal. Goalkeeper goals run autonomously across many turns with checkpoint validation and judge-gated completion.
Scanned 5/27/2026
Install via CLI
openskills install itsuzef/goalkeeper---
name: goal
description: Set or check status of a durable goalkeeper goal. Use this skill when the user invokes /goal "<objective>" to start a new contract-driven goal, or /goal with no arguments to see status of the currently active goal. Goalkeeper goals run autonomously across many turns with checkpoint validation and judge-gated completion.
---
You are operating the **goalkeeper** skill — durable, contract-driven goal execution with judge-gated completion. This skill is invoked when the user runs `/goal` or `/goal "<objective>"`.
## Execution modes (v0.3+)
Goalkeeper goals run in one of two execution modes depending on how the goal was activated:
### Inline mode (standalone `/goal` / `/goal "<objective>"`)
Main conversation context runs the full Execution Loop (do work → checkpoint → run validator → invoke judge → branch on verdict). Use ScheduleWakeup to pace iterations across long-running validators. This is the default when the user invokes `/goal` directly.
### Subagent mode (chain-driven via `/goal-chain`)
When `/goal-chain` is the orchestrator, the chain spawns a **fresh-context executor subagent** per goal. Main context only orchestrates — it does NOT do the per-goal implementation work. The executor subagent reads contract + log, does the work end-to-end, runs validator, and returns a structured summary. Main context then spawns the judge subagent, applies the verdict, and either advances the cursor or re-spawns the executor with the judge's fix-list.
This is the load-bearing change in v0.3 that lets multi-goal chains run autonomously without main context aging out. Main context cost per goal drops from "all the work + judge spawn" (tens of thousands of tokens accumulating per goal) to "executor return + judge spawn" (~10K tokens per goal, flat). A 9-goal chain that previously needed 9 fresh sessions can now complete in one main-context session.
The protocol described in the Execution Loop section below applies in BOTH modes — the difference is who runs it:
- Inline mode: main conversation runs the loop, with ScheduleWakeup pacing.
- Subagent mode: each goal's executor subagent runs the loop in its own context, with no ScheduleWakeup needed (the subagent runs end-to-end then returns).
Both modes use the same state.json / log.md / active.json files. The only mode-specific divergence is whether the judge is invoked by the loop (inline) or by the chain orchestrator after executor return (subagent). See `goal-chain/SKILL.md` Steps 6–8 for the subagent-mode orchestration details.
## Canonical state shapes
Single source of truth for the JSON files goalkeeper reads and writes. Other skills (goal-clear, goal-judge, goal-chain) MUST conform to these shapes — drift here is the source of cross-skill bugs.
### `.claude/goals/active.json`
Two shapes only — active or terminal.
**Active** (a goal is currently running):
```json
{
"slug": "<slug>",
"activated_at": "<ISO8601>",
"chain": "<chain-name>"
}
```
The `chain` field is OPTIONAL — present only when activation was driven by `/goal-chain` (start mode or advance mode). Standalone goals omit it.
**Terminal** (no active goal):
```json
{
"slug": null,
"ended_at": "<ISO8601>",
"ended_reason": "done" | "cleared" | "chain_completed" | "aborted",
"previous_slug": "<slug>",
"previous_chain": "<chain-name>"
}
```
`slug`, `ended_at`, and `ended_reason` are REQUIRED on terminal. `previous_slug` SHOULD be set when the last activity was a single goal or chain link. `previous_chain` SHOULD be set when a chain just ended (`chain_completed` or `aborted`).
### `.claude/goals/<slug>/state.json`
```json
{
"status": "active" | "paused" | "done" | "needs_human",
"rejection_count": <int>,
"started_at": "<ISO8601>",
"started_at_commit": "<git rev-parse HEAD or null>",
"started_at_dirty_paths": ["<paths from git status --porcelain at activation>"],
"chain_step": <int>,
"last_checkpoint_at": "<ISO8601 or null>",
"last_validator_result": "pass" | "fail: <reason>" | null,
"last_judge_verdict": "approve" | "reject" | null,
"approved_at": "<ISO8601>",
"paused_at": "<ISO8601>",
"resumed_at": "<ISO8601>",
"needs_human_at": "<ISO8601>",
"validator_baseline_result": "pass" | "fail" | "not_runnable" | null,
"validator_baseline_failing_paths": ["<path>"]
}
```
`status`, `rejection_count`, `started_at`, `started_at_commit`, `started_at_dirty_paths` are REQUIRED on activation. `chain_step` SHOULD be present when the goal is part of a chain (denormalized for log clarity; chain.json is the source of truth for cursor). Timestamp fields populate as the goal transitions: `approved_at` on judge approve, `paused_at`/`resumed_at` on `/goal-pause`/`/goal-resume`, and `needs_human_at` when `rejection_count` reaches `max_rejections` and status flips to `needs_human`.
`validator_baseline_result` and `validator_baseline_failing_paths` are populated by `/goal-prep` if it ran the validator once at activation baseline (which prep already does to confirm the command is runnable). When set, the judge treats `failing_paths` as pre-existing — a goal-end validator failure on those same paths is not the goal's fault. When null (validator was not run at prep, or prep is skipped), the judge has no pre-existing baseline to subtract from and treats validator failures as goal-caused.
### `.claude/goals/chain.json`
```json
{
"name": "<chain name>",
"slugs": ["<slug>", "..."],
"cursor": <int>,
"status": "active" | "done" | "aborted",
"started_at": "<ISO8601>",
"completed_at": "<ISO8601 or null>",
"source_file": "<absolute path>",
"link_approvals": [
{"slug": "<slug>", "approved_at": "<ISO8601>"}
]
}
```
`link_approvals` accumulates one entry per judge-approved link. Provides chain-level visibility independent of per-link state.json files.
### `.claude/mission.json` (v0.2 — supervisor layer)
```json
{
"name": "<from mission.md heading>",
"status": "active" | "done" | "escalated",
"started_at": "<ISO8601>",
"completed_at": "<ISO8601 or absent>",
"goals_completed": [
{"slug": "<slug>", "result": "approved" | "cleared", "rejection_count": <n>, "ended_at": "<ISO8601>"}
],
"supervisor_verdicts": [
{"at": "<ISO8601>", "prior_slug": "<slug>", "verdict": "proceed" | "done" | "escalate", "next_objective": "<if proceed>", "escalation": "<if escalate>"}
]
}
```
The mission layer sits ONE LEVEL ABOVE chains. Missions adaptively decide the next goal based on the prior goal's actual output, where chains commit to a linear sequence at chain-start. Companion files: `mission.md` (user-authored charter — required to invoke `/goal-supervisor`), `mission-log.md` (append-only mission-level audit trail), `mission-completed.md` (final snapshot written when supervisor verdict is `done`). See `skills/goal-supervisor/SKILL.md` for the full state machine.
## Decide mode from args
- `args` non-empty → **set mode** (start or resume a goal)
- `args` empty → **status mode** (report on active goal)
## Status mode
1. Read `.claude/goals/active.json`. If missing or `slug` is null, output: `No active goal. Run /goal-prep "<rough idea>" or /goal "<objective>" to start one.` and stop.
2. Read `.claude/goals/<slug>/contract.md`, `state.json`, and the last 10 entries of `log.md`.
3. Print a compact status block:
```
Goal: <slug>
Objective: <one-line>
Status: <status> Rejections: <n>/<max>
Started: <ISO8601> Elapsed: <human readable>
Validator: <last_validator_result>
Judge: <last_judge_verdict>
Last log: <timestamp> — <one-line summary>
```
4. If `status == needs_human`, surface the latest judge fix-list verbatim and tell the user: fix the listed items, then run `/goal-resume` to continue or `/goal-clear` to abandon.
## Set mode
1. **Derive slug:** extract or generate a kebab-case slug from the objective (≤64 chars, lowercase, alphanumeric and hyphen only). If the user passed an explicit `--slug=<value>`, use that.
2. **Contract resolution:**
- If `.claude/goals/<slug>/contract.md` exists, treat the request as a resume: skip prep, jump to step 3.
- If it does not exist, **auto-route to `/goal-prep`** with the same objective. Do not write a thin one-line contract — prep is mandatory because the contract IS the spec. After prep completes and writes the contract, return here.
3. **Activate:**
- Ensure `.claude/goals/` exists. If creating it for the first time, also create `.claude/goals/.gitignore` containing `*` and `!shared/` and `!.gitignore` so personal goals stay private but a `shared/` subdir can be opt-in committed.
- **Capture the git baseline.** If the working directory is a git repo (`git rev-parse --is-inside-work-tree` succeeds), capture `git rev-parse HEAD` as the goal's diff origin. If the tree is dirty, also capture `git status --porcelain` as a snapshot of the dirty paths so the judge can distinguish goal work from pre-existing changes. If not in a git repo, set both fields to `null`.
- Write `.claude/goals/<slug>/state.json`:
```json
{
"status": "active",
"rejection_count": 0,
"started_at": "<ISO8601 now>",
"started_at_commit": "<git rev-parse HEAD or null>",
"started_at_dirty_paths": ["<paths from git status --porcelain at activation>"],
"last_checkpoint_at": null,
"last_validator_result": null,
"last_judge_verdict": null
}
```
- Write `.claude/goals/active.json` per the canonical active shape. If the activation is driven by `/goal-chain`, include `"chain": "<chain-name>"`; otherwise omit the field.
- Append to `log.md`:
```
## <ISO8601> — activated
Starting work on: <objective>
Baseline commit: <short SHA or "no-git">
```
4. **Begin the execution loop** (next section).
## Execution loop
This block runs on activation AND on every ScheduleWakeup re-entry.
1. Read the active contract: `.claude/goals/active.json` → `<slug>` → `contract.md` and `state.json`.
2. If `state.status != active`, stop. Do not schedule another wakeup.
3. Read recent log entries to know where work left off.
4. **Do real work** on the objective for one checkpoint's worth of progress (size per `checkpoint_cadence` in the contract — e.g. ~5 file edits, ~20 minutes of effort, or one logical sub-task).
5. **Checkpoint:** append to `log.md`:
```
## <ISO8601> — checkpoint
<one short paragraph: what changed, files touched, decisions made, what's next>
```
6. **Run validator:** execute `validator.command` from frontmatter. Capture stdout+stderr. Honor `timeout_seconds`. Interpret per `validator.success` (default `exit_zero`). Update `state.last_validator_result` to one of `pass`, `fail: <short reason>`. Update `state.last_checkpoint_at`.
7. **Branch on validator:**
- **Failed:**
- *Inline mode:* append a one-line failure summary to log. Schedule next iteration via `ScheduleWakeup` with delay from `wakeup_seconds` (contract) or pick cache-aware default (see below). Wakeup prompt:
```
Continue active goalkeeper goal — read .claude/goals/active.json and proceed per the goal skill execution loop.
```
- *Subagent mode:* fix the failure and re-run (validator failures inside a subagent don't need ScheduleWakeup — the subagent runs end-to-end in one turn). If after a reasonable attempt budget (~3-5 inner attempts) the validator still fails, return to the chain orchestrator with `STATUS: validator_fail` + a clear BLOCKERS field (per `goal-chain/SKILL.md` Step 6 directive). Main context handles the user-surface.
- **Passed:**
- *Inline mode:* invoke the **goal-judge** skill (do this by writing a sentinel `.claude/goals/<slug>/.judge-pending` and immediately running the judge skill in this same turn). Judge will read contract + `git diff` + log and return approve/reject.
- *Subagent mode:* do NOT invoke the judge yourself. Return to the chain orchestrator with `STATUS: validator_pass` + the structured summary fields. Main context spawns the judge and applies the verdict.
8. **Branch on judge verdict** *(inline mode only — in subagent mode the chain orchestrator handles this section per `goal-chain/SKILL.md` Step 8)*:
Judge writes verdict to `state.last_judge_verdict` and a fix-list to `log.md` if reject.
- **Approve:**
- If `.claude/goals/chain.json` exists and contains the active slug at the cursor: this should not happen in inline mode, but if it does, defer to the **goal-chain** skill advance flow.
- Otherwise: set `state.status=done`, append `## <ISO8601> — done` to log, null out `active.json` (`{"slug": null}`). Surface a one-paragraph completion summary to the user.
- **Reject:**
- Append the judge's fix-list to log under `## <ISO8601> — judge rejected`.
- `state.rejection_count += 1`.
- If `rejection_count >= max_rejections` (default 5): set `state.status=needs_human`, append `## <ISO8601> — paused (max rejections)`, stop. Do NOT schedule next wakeup. Surface to user.
- Otherwise: schedule next iteration with the fix-list as the next turn's primary task. Wakeup prompt:
```
Continue active goalkeeper goal — judge rejected the last attempt. Read the most recent "judge rejected" block in .claude/goals/<slug>/log.md and address each fix-list item. Then proceed per the goal skill execution loop.
```
## Cache-aware wakeup delay
The Anthropic prompt cache has a 5-minute TTL. Pick `delaySeconds` deliberately:
- **60–270s** — validator runs fast, cache stays warm. Right for tight iteration.
- **1200–1800s** — validator is slow (long test suites, builds, evals) or work is genuinely idle.
- **Avoid 300s** — pays cache miss without amortizing it.
- Honor `wakeup_seconds` from the contract verbatim if set.
- The runtime clamps to [60, 3600].
## Hard rules
- **Never modify `contract.md` mid-run.** If the spec is wrong, stop and ask the user to amend it explicitly via /goal-clear + new prep.
- **Never skip the validator.** Never declare "done" without judge approval.
- **The log is append-only.** Never delete or rewrite past entries.
- **One execution loop per goal.** Do not spawn parallel iterations on the same slug.
- **Rejection counter only resets** on judge approval or `/goal-clear`.
- **Anti-placeholder:** do not stub, mock, or skip work to make the validator pass. The judge will catch placeholders and reject. (Borrowed from the Ralph pattern.)
No comments yet. Be the first to comment!