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):
{
"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):
{
"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
{
"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
{
"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)
{
"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
argsnon-empty → set mode (start or resume a goal)argsempty → status mode (report on active goal)
Status mode
-
Read
.claude/goals/active.json. If missing orslugis null, output:No active goal. Run /goal-prep "<rough idea>" or /goal "<objective>" to start one.and stop. -
Read
.claude/goals/<slug>/contract.md,state.json, and the last 10 entries oflog.md. -
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> -
If
status == needs_human, surface the latest judge fix-list verbatim and tell the user: fix the listed items, then run/goal-resumeto continue or/goal-clearto abandon.
Set mode
-
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. -
Contract resolution:
- If
.claude/goals/<slug>/contract.mdexists, treat the request as a resume: skip prep, jump to step 3. - If it does not exist, auto-route to
/goal-prepwith 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, r
- If