From goal-mode
Use when an active goal-mode plan-tree is driving the conversation (Stop hook fires continuation prompts, lifecycle is pursuing/paused/awaiting-manual-approval/blocked) OR before /goal-plan, /goal-start, /goal-approve, /goal-resume, /goal-abandon, /goal-clear — covers tag emission discipline, escape-hatch protocol, lifecycle states, anti-patterns, multi-session cross-project isolation, and recovery paths.
How this skill is triggered — by the user, by Claude, or both
Slash command
/goal-mode:using-goal-modeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
<SUBAGENT-STOP>
This skill teaches you (the controller agent driving a goal) how to interact with the goal-mode engine without breaking it, without spam-looping the chat, and without falsely declaring achievement. Read it once per conversation when a goal is active; the rules don't change between turns.
The full functional reference is in commands/goal-help.md (the slash command /goal-mode:goal-help). This skill focuses on behavior: when to do what, what NOT to do, and how to recover from edge cases.
In v3.0+, the Stop hook returns null stdout on lifecycle=pursuing by default. The agent drives the goal explicitly via slash commands. Stop hook does NOT inject continuation prompts every turn. No more silence loops, no more multi-session leakage spam — the agent is in control of when state advances.
Inspect the cursor:
/goal-mode:goal-current
Returns the cursor task with checkbox-style AC coverage. Use --json for scripting or --as-builtin for piping into Claude Code's built-in /goal "..." command.
Do the work in normal Claude Code mode. Read files, edit code, run tests, etc. — no special tag emission required.
Record evidence per acceptance criterion:
/goal-mode:goal-evidence-add --criterion N --file path[:line] --note "..."
/goal-mode:goal-evidence-add --criterion N --command "npm test -- foo" --exit-code 0 --note "..."
The engine validates lifecycle='pursuing' and cursor.status ∈ {pursuing, review-pending} per call.
Claim achievement:
/goal-mode:goal-achieve
cursor.review[] is empty → marks achieved + advances cursor.review-pending.If review-pending: dispatch reviewers + record verdicts:
/goal-mode:goal-review-request
Prints the reviewer list + evidence summary + audit-instructions template. For each reviewer, dispatch via the Agent tool:
Agent({
subagent_type: "<reviewer>",
description: "Review task <cursor-id>",
prompt: "<audit-instructions template body>"
})
For each verdict:
/goal-mode:goal-submit-verdict --agent <reviewer> --status GO|NOGO|REVISE --text "..."
The CLI scans the live transcript for a real Agent(subagent_type=<name>) dispatch — fabricated verdicts are rejected with independence violation.
Repeat from step 1 for the next cursor task.
If a required subagent_type isn't installed in the current environment, submit a REVISE verdict with text starting "unavailable; user must run /goal-approve". The engine routes this to lifecycle awaiting-manual-approval (cursor blocked but recoverable). User then runs /goal-mode:goal-approve <task-id> to manually approve.
For simple work loops, pipe /goal-mode:goal-current --as-builtin output into Claude Code's built-in /goal "..." command. Built-in /goal handles the work loop via Anthropic's Haiku evaluator; goal-mode tracks structured progress, reviewers, and budget in parallel. After built-in /goal completes, run /goal-mode:goal-evidence-add + /goal-mode:goal-achieve to record progress in the structured tracker.
If you set stopHookDriver: true in .claude/goals/active/config.json (per-project) or ~/.claude/plugins/goal-mode/config.json (per-user), the legacy v2 driver kicks back in: Stop-hook injects continuation prompts each turn, and the agent emits XML tags (<evidence>, <task-status>, <audit-verdict>) as before. See the Legacy v2 tag-emission workflow section below for details.
lifecycle == 'pursuing'. The Stop hook reads on-disk state, computes the next continuation prompt, and injects it as the next user-turn input. Context loss between turns is harmless — state lives on disk.state.json or tree.json directly. You emit structured tags in your text. The engine parses tags and mutates state.GO, OR when the user runs /goal-mode:goal-approve. The engine prevents proxy-signal collapse (you can't fabricate verdicts on a reviewer's behalf).budget-limited lifecycle.| Lifecycle | Meaning | What you do |
|---|---|---|
draft | /goal-plan wrote a tree, not validated | Wait for /goal-approve-plan |
approved | Plan locked, no budgets yet | Wait for /goal-start |
pursuing | Active work | Read Stop-hook prompt, do work, emit evidence + task-status |
paused | User explicitly paused | Don't fire Stop work (engine returns null stdout). Wait for /goal-resume |
awaiting-manual-approval | v2.0.4 — escape-hatch from unavailable reviewer | Engine has STOPPED firing Stop-hook prompts. You wait silently for user /goal-mode:goal-approve <task-id> or /goal-abandon. Do not re-emit tags trying to fix this — environmental issue |
achieved | Every leaf task achieved; terminal | Engine renders final-summary.md once; no further turns |
unmet | 3 consecutive blocks/NOGOs OR /goal-abandon; terminal | Engine renders unmet-summary.md once; recovery via /goal-clear --archive + replan |
budget-limited | Iter/tokens/wallclock cap hit; terminal | Engine renders budget-limit.md; recovery via /goal-resume with fresh budget OR /goal-clear |
Note: This section applies under legacy
stopHookDriver: trueconfig. v3 default workflow uses explicit CLI verbs — see top of this document.
The engine parses these tags from your text. Get them wrong and the engine ignores them silently.
<evidence file="path/to/file" line="N" criterion="i" note="short proof"/>
<task-status>pursuing|achieved|blocked</task-status>
<evidence> MUST have an integer criterion="i" attribute, where i is in [0, len(acceptance_criteria)). Missing or out-of-range → silently dropped from coverage check, but kept in the cursor's evidence list (the engine logs it; reviewers can read it).achieved ONLY when every criterion has at least one evidence entry AND every required reviewer returned GO. The engine refuses to mark achieved otherwise — you'll get the same prompt next turn.<task-status> values are case-insensitive (v2.0.3 fix): ACHIEVED, Achieved, achieved all normalize to achieved.<review-request agents="reviewer-1,reviewer-2"/> <!-- self-closed; comma-separated -->
<blocker>reason text</blocker> <!-- pair with task-status:blocked -->
<audit-verdict agent="reviewer-x" status="GO|NOGO|REVISE">verdict body</audit-verdict>
<review-request> triggers transition pursuing → review-pending when all criteria are covered. Optional — if a task's review array is empty in the plan, the cursor advances on <task-status>achieved</task-status> immediately.<audit-verdict> is for reviewers, not the controller. You (controller) emit it ONLY when the reviewer subagent has actually returned a verdict and you are relaying it (with the same agent name + status the subagent gave).The Stop-hook templates ask for this format:
**Retry — taskName()**
What I changed:
- bullet 1
- bullet 2
How it now covers the criteria:
- AC#0 — short summary
- AC#1 — short summary
<details>
<summary>engine evidence (machine-parsed)</summary>
<evidence file="path" line="N" criterion="0" note="..."/>
<evidence file="path" line="N" criterion="1" note="..."/>
<task-status>achieved</task-status>
</details>
<details> → what the engine parsesinline backticks — the Stop hook strips code regions before parsing (stripCodeRegions). Example tags in prose-rendered prompts are intentionally ignored; real tags must be in prose.The engine enforces that <audit-verdict> tags can only be ACCEPTED when there is a matching Agent(subagent_type=...) invocation in the same turn's transcript. This is the "scannedAgents" check.
What this means for you:
Agent({subagent_type: "<reviewer-name>", ...}). The engine reads the transcript and verifies the dispatch happened.state.history with payload.rejected: true and don't advance the cursor.<audit-verdict agent="X" status="GO">looks fine to me</audit-verdict> and the engine would honor it. The new check kills that path.Scenario: A reviewer's subagent_type is not registered as an Agent in the current Claude environment, so Agent({subagent_type: "...", ...}) returns Agent type not found. This happens when the project's plan refers to a reviewer that exists only as a Skill (markdown file at ~/.claude/skills/<name>/) but lacks a paired Agent file (markdown file at ~/.claude/agents/<name>.md with matching name: frontmatter).
These are two different things in Claude Code:
| Skill | Agent | |
|---|---|---|
| File location | ~/.claude/skills/<name>/SKILL.md | ~/.claude/agents/<name>.md |
| Activated by | Skill tool with skill: "name" | Agent tool with subagent_type: "name" |
| Runs in | Current session, instructions inlined | Separate subagent context |
| For reviews | NO — can't return a verdict to the parent | YES — independent subagent verdict |
If a goal-mode plan declares a reviewer that only exists as a Skill, the engine's Agent(subagent_type=...) dispatch will fail. You need to either:
~/.claude/agents/<name>.md with YAML frontmatter name: <name> and a description + tool whitelist + body (can mirror the skill's content). Then Agent({subagent_type: "<name>"}) resolves and the reviewer-independence check passes.When you've tried Agent({subagent_type: "X"}) and got Agent type not found, emit:
<audit-verdict agent="X" status="REVISE">unavailable; user must run /goal-approve</audit-verdict>
Exact format requirements:
status="REVISE" (not NOGO, not GO)unavailable (case-insensitive, optional leading whitespace). The engine's regex is /^\s*unavailable\b/i.user must run /goal-approve is conventional but not strictly required by the regex — anything starting with "unavailable" matches.In v2.0.4:
blocked with blocker_reason listing the unavailable reviewer(s) + recovery options.state.lifecycle to awaiting-manual-approval (terminal-but-recoverable).continuation-blocked.md ONE more time so the user sees the recovery instructions./goal-mode:goal-approve <task-id> (manual override) OR register the missing Agent file OR /goal-mode:goal-abandon.❌ Re-emitting <task-status>blocked</task-status> over and over with the same blocker reason. Pre-v2.0.4 this ticked review_attempts toward the 3-strike unmet threshold and killed the goal from purely environmental cause. v2.0.4's lifecycle gate makes this impossible (the Stop hook stops firing). But: the underlying anti-pattern is "trying to fix an environmental problem from code". You can't conjure a missing Agent file into existence. Stop trying. Wait for the user.
awaiting-manual-approvalThe lifecycle has structural effects:
warn with action.What YOU (controller) do:
/goal-mode:goal-status or /goal-mode:goal-doctor and explain the three recovery paths:
/goal-mode:goal-approve <task-id> — manual GO, restores lifecycle=pursuing, advances cursor.~/.claude/agents/<name>.md for the missing reviewer, then /goal-mode:goal-abandon + replan (the existing escape-hatch can't be retried in-place — but a fresh goal will dispatch the now-available reviewer normally)./goal-mode:goal-abandon if the goal is no longer wanted.Goal-mode is scoped per-project via <projectRoot>/.claude/goals/active/. The hooks resolve projectRoot from Claude Code's stdin.cwd field, NOT process.cwd() (v2.0.2 fix). This means:
/Users/foo/projectA, that session's stdin.cwd is /Users/foo/projectA, and all goal-mode work happens against projectA/.claude/goals/active/.cd-ing inside the shell during the session does NOT change which project the goal-mode hooks read from — stdin.cwd is set per-event by Claude Code, not by your shell's cwd.Implication for you (controller):
/goal-mode:goal-pause the current goal so its Stop hook stops firing.| Situation | What to do |
|---|---|
| Task achieved, want to advance | Emit <evidence ...> + <task-status>achieved</task-status> |
| Reviewer needed, but criteria met | Emit <review-request agents="..."/>, then on next turn dispatch Agent({subagent_type: "..."}) and relay verdicts |
| Reviewer NOGO/REVISE | Cursor returns to pursuing, review_attempts++. After 3 cycles → blocked then unmet |
| Reviewer subagent_type unavailable | Emit escape-hatch verdict (see above). Engine → awaiting-manual-approval. Wait for user |
| Genuinely can't make progress on a task | Emit <task-status>blocked</task-status> + <blocker>reason</blocker>. After 3 consecutive blocks → unmet |
| Budget about to exhaust | Doctor's budget-headroom warns at 75%, fails at 95%. Suggest /goal-mode:goal-pause for scope review |
| State.json or tree.json corrupt | recoverCacheFromEvents(projectRoot) in engine; or doctor's state-loadable check + .broken-* backups |
Goal already unmet and you want to retry | /goal-mode:goal-clear --archive, then replan (/goal-plan-from-file or /goal-plan) |
| Need to stop a goal permanently | /goal-mode:goal-abandon --reason "..." (lifecycle → unmet) |
state.json, tree.json, or events.jsonl. The engine validates schemas; corrupt files go to .broken-<ts>-<seq>.json. Use commands.<task-status>achieved</task-status> without <evidence criterion="i"/> for every criterion of the cursor task. The engine refuses and re-fires the same prompt.```) or backtick spans ( ). The Stop hook strips code regions before parsing.<audit-verdict> tags. The engine's reviewer-independence check rejects verdicts without matching Agent dispatch. Even for reviewers the engine knows about — you MUST actually dispatch the Agent.awaiting-manual-approval, stop emitting goal-mode tags for that task. Wait./goal-mode:goal-clear without --archive unless the user explicitly confirmed. clear is permanent deletion.<promise> or other "Ralph-style" escape phrases. Goal-mode parses only documented tags.If you encounter behavior matching these old-version symptoms, suggest the user upgrade:
| Symptom | Affected versions | Fixed in |
|---|---|---|
| Infinite loop after reviewer unavailable | 2.0.0 (only) | 2.0.1 |
| Same goal's continuation appears in OTHER projects | ≤ 2.0.1 | 2.0.2 |
SessionStart renders review/blocked template with empty {{audit_instructions}} | ≤ 2.0.2 | 2.0.3 |
.claude/goals/active/ dirs created in every project the user touches | ≤ 2.0.2 | 2.0.3 |
| Stop hook reads entire transcript every tick (laggy on long sessions) | ≤ 2.0.2 | 2.0.3 |
Tokens count silently decreases after /compact | ≤ 2.0.2 | 2.0.3 |
Doctor's budget-headroom shows FAIL on achieved goal | ≤ 2.0.2 | 2.0.3 |
| Repeated "Не лезу" / minimum-text loop after escape-hatch | ≤ 2.0.3 | 2.0.4 |
Goal terminates unmet from environmental cause (missing reviewer Agent) | ≤ 2.0.3 | 2.0.4 |
Upgrade procedure:
cd /path/to/claude-code-goal-mode && bash install.sh
# then: full restart of Claude Desktop (kill + relaunch, not just plugin reload)
<projectRoot>/.claude/goals/active/
├── tree.json # plan tree, zod-validated, v2 schema
├── state.json # runtime state (cursor, lifecycle, budget, history)
├── plan.md # human-readable plan view
├── notes.md # append-only digest (one line per Stop-hook fire)
├── events.jsonl # ADR-0001 event log (canonical truth)
├── .transcript-cache.json # v2.0.3 incremental scan checkpoint
├── .lock # ADR-0002 advisory lock (only present during writes)
├── audits/ # per-verdict JSON files
└── snapshots/ # periodic full-state snapshots for fast recovery
<projectRoot>/.claude/goals/archive/
└── <ISO-timestamp>-<goal_id>/ # created by /goal-clear --archive
/goal-mode:goal-status — current cursor, lifecycle, budget, last events/goal-mode:goal-tree — ASCII tree with status glyphs (✓ achieved / ▶ pursuing / 🔵 review-pending / ⛔ blocked / · pending)/goal-mode:goal-doctor — 13 health checks. Run this when anything looks wrong./goal-mode:goal-doctor --fix — apply safe auto-fixes (broken-backups GC, pre-migration backup retention)/goal-mode:goal-approve <task-id> — manual GO override, also handles awaiting-manual-approval/goal-mode:goal-pause — pause Stop hook firing/goal-mode:goal-resume — restore pursuing (refuses if any budget exhausted)/goal-mode:goal-abandon --reason "..." — terminate as unmet/goal-mode:goal-clear --archive — permanently delete active goal dir (archives first)When a Stop hook fires, you receive a continuation prompt with one of three shapes:
continuation.md (cursor status=pursuing) — normal work, emit evidence + task-statuscontinuation-review.md (cursor status=review-pending) — dispatch reviewer Agent(s), relay verdicts via <audit-verdict>. If you see ## ⚠ Rejected verdicts from this review cycle, the prior turn's verdicts were rejected — dispatch the missing Agent THIS turn before re-emitting.continuation-blocked.md (cursor status=blocked) — either retry with fresh evidence (real work) or re-emit <task-status>blocked</task-status> with a new <blocker>. If the prompt has a ## ⚠ Reviewer agent unavailable in this environment section — this is the escape-hatch case. Stop emitting tags; wait for user /goal-approve.There's also final-summary.md, unmet-summary.md, budget-limit.md — terminal templates. After these, no further Stop hooks fire.
When you dispatch a reviewer:
Agent({
subagent_type: "<reviewer-name>",
description: "Review task <task-id>",
prompt: "<audit instructions body — comes from audit-instructions.md template, rendered in continuation-review.md>"
})
The reviewer subagent runs in isolation. It returns a verdict via its own output. You relay it via:
<audit-verdict agent="<reviewer-name>" status="GO|NOGO|REVISE">
<full text of reviewer's verdict>
</audit-verdict>
The engine matches agent against the subagent_types you actually dispatched this turn. If you relay a verdict from a reviewer you didn't dispatch, it's rejected as fabricated.
Multi-reviewer: dispatch each one (one Agent call per reviewer) within the same turn, then emit one <audit-verdict> per reviewer. The engine requires every required reviewer to return GO before advancing.
Invoke using-goal-mode skill:
lifecycle == 'pursuing' (Stop hook prompts are firing)./goal-mode:goal-plan, /goal-mode:goal-start, /goal-mode:goal-approve, /goal-mode:goal-resume, /goal-mode:goal-abandon, or /goal-mode:goal-clear.## ⚠ Reviewer agent unavailable in this environment or ## ⚠ Rejected verdicts from this review cycle.Skip the skill (SUBAGENT-STOP at top) if you were dispatched as a reviewer subagent — your job is to emit <audit-verdict> per commands/goal-review.md, not to drive the goal.
For deep dives into tag parsing semantics, the parser's edge cases (case-insensitivity in v2.0.3+, attribute quoting rules, code-fence stripping), and the exact regexes the Stop hook uses, see the goal-mode-tag-discipline skill.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub lokafinnsw/claude-code-goal-mode --plugin goal-mode