Give it a goal. It writes a contract, does the work, reviews itself with a bench of
specialist subagents, and cannot tell you it is finished until those reviews
actually pass. Reach for rm -rf mid-run and the command is stopped before it
executes.
npm install -g opencode-goal-modeMost coding agents will cheerfully announce success over a half-finished feature and a red test suite. Goal Mode ends that. It moves the discipline out of the prompt — where a confident model simply talks past it — and into the harness, where it is enforced in code. A "Goal Completed" claim is intercepted and rewritten to "Goal Not Completed" unless every required review gate holds a fresh pass. Dangerous shell commands are blocked before they ever run.
It is the difference between asking an agent to be careful and making it.
Goal Mode is a drop-in OpenCode package for people who want agentic coding to be auditable rather than theatrical. It gives your agent a contract, a live ledger, a specialist review bench, and a command guard — all of which live outside the model's memory, so the model cannot argue its way past them. The result is a sharper workflow for real repositories: fewer premature victory laps, fewer stale approvals, and a clear trail of what changed, how it was checked, and which gates passed.
What you get after installing:
- A primary
goalagent that turns a request into acceptance criteria before it edits. - Programmatic review cycles that the guard launches automatically when the work is ready.
- Freshness tracking that invalidates every review pass the moment the next edit lands.
- A quote-aware shell analyzer that blocks destructive and remote-execution commands.
- A live TUI sidebar that turns the hidden ledger into visible progress.
- Support for any agentic goal, not only writing code: a research, analysis, explanation, or planning goal is gated on its recorded evidence rather than a diff, so a correct text deliverable can complete without being blocked by an empty
git diff.
The agent tries to declare victory early. The guard intercepts the claim and replaces it with the truth:
- Goal Completed
+ Goal Not Completed
+
+ Goal Guard blocked completion: required review gates are missing or stale
+ (goal-security-reviewer, goal-final-auditor). State: active=true; dirty=true;
+ reviewCycles=1; missingGates=goal-security-reviewer goal-final-auditorThe agent reaches for something irreversible. The guard stops it before it runs:
$ rm -rf build
Goal Guard blocked a destructive or high-risk bash command: `rm -rf build`
(rm with recursive force deletion). Use a safer, reversible command or ask the
user to confirm.
While a goal runs, Goal Mode takes over the TUI sidebar with a live, evidence-aware todo list: the goal title, gate progress, and a row for each acceptance criterion and outstanding reviewer, each one resolving as the work is verified.
"Done" actually means done. Completion is gated on real review verdicts, not
self-assessment. The model cannot emit Goal Completed until every required reviewer
has returned Verdict: PASS after the last edit — and the claimed Review cycles: N
must match the counter the guard kept itself.
The reviews run themselves. When the agent stops with work outstanding, the guard launches the reviewer subagents on its own — security, diff, verification, and more — reads their verdicts, and loops fix then review until they pass. You never depend on the model remembering to check its own work.
One edit reopens the gates. Approvals are stamped with a monotonic sequence, so any change after a review immediately goes stale and forces the relevant reviews to re-run. There is no slipping a "fix" in after the green light.
It knows which experts to call. Touch authentication and the security reviewer becomes mandatory. Touch a migration and the data reviewer joins. API, performance, tests, UX, operations, documentation, and quality each have a gate that is required automatically from your goal and your diff.
Your repository survives. A real shell tokenizer — not a brittle regular
expression — blocks destructive commands even when they are disguised: $(rm -rf …),
bash -c "…", /bin/rm, busybox rm -rf, git reset --hard, and curl | sh.
Harmless look-alikes such as git checkout -b pass straight through.
It does not quit on you. An idle but unfinished goal is pushed forward automatically — told exactly what remains — until it is genuinely complete, with hard caps and a no-progress breaker so it can never spin.
Measured on 704 real-world commands from tldr-pages
(common, Linux, and macOS pages) — commands written by hundreds of contributors who
have never seen this guard. Reproduce them with npm run bench.
| On 704 commands it has never seen | Regex baseline | Goal Mode |
|---|---|---|
| Dangerous commands caught | 53.8% | 92.3% |
| Safe commands wrongly blocked | 0.2% | 0.8% |
Goal Mode catches roughly three-quarters more of the dangerous commands a regex
baseline misses, in exchange for a small, deliberate increase in false positives
(eight commands of the 704 remain unflagged, mostly single-target rm). Classification
is effectively free: about 1.35 microseconds per command — over 700,000 commands a
second.
| Capability | Goal Mode | Claude Code | Codex |
|---|---|---|---|
| Blocks a premature "done" out of the box | Enforced | Custom hook required | Review is advisory |
| Edits auto-invalidate stale approvals | Enforced | Not built in | Not built in |
| Specialist reviews auto-required from the task | Enforced | Not built in | Not built in |
| Destructive-command blocking by a real shell parser | Enforced (tokenizer) | Partial (regex) | Partial (sandbox) |
Claude Code and Codex are capable tools with real mechanical surfaces of their own; this is a comparison of one specific axis — built-in, enforced goal discipline. The full side-by-side, with sources and review dates, is in research/goal-mode-comparison.md.
One command. Requires Node 20.11 or newer and OpenCode. Supported on macOS and Linux:
npm install -g opencode-goal-modeThen restart OpenCode. A global install runs the installer automatically through
postinstall; re-run opencode-goal-mode --global if auto-setup did not finish, or
add --force to replace files you have edited. The installer copies the Goal agent,
its reviewer subagents, the slash commands, the customization skill, and the guard
plugin into ~/.config/opencode, and registers the live sidebar in tui.json. In the
agent picker you will see a single entry, goal — the reviewers are subagents it
drives for you. The installer is idempotent (re-run to upgrade), records a manifest,
never overwrites files you have edited unless --force is passed, and --uninstall
removes exactly what it installed. Goal Mode uses whatever model and provider OpenCode
is already configured with.
Other ways to install
# Preview first — no writes on --dry-run
opencode-goal-mode --global --dry-run
# One-off with npx (no global package needed)
npx opencode-goal-mode --global
# Into a single project (writes ./.opencode)
npx opencode-goal-mode
# Remove everything it installed
opencode-goal-mode --global --uninstall
# From source
git clone https://github.com/devinoldenburg/opencode-goal-mode
cd opencode-goal-mode && npm ci && npm run install:global--global writes to ~/.config/opencode; no flag writes to ./.opencode; --target
writes to a directory you pass. On upgrade it replaces the files it owns but refuses to
overwrite files you have modified unless --force is passed.
# After installing and restarting OpenCode, confirm the primary agent loaded:
opencode agent list | grep '^goal 'opencode agent list shows goal (primary) — the single agent you select. The
goal-* reviewer specialists also appear, each tagged (subagent); those are invoked
by the Goal agent, not chosen by you. A bare grep goal therefore prints the whole
goal-* family, so the anchored grep '^goal ' above isolates just the primary.
Then, inside OpenCode, give it a goal:
/goal add rate limiting to the login endpoint and prove it works
It writes a contract, delegates research to subagents, implements, and verifies — then
stops and lets the guard run the reviews. It will not say Goal Completed until they
pass. To feel the guardrail directly, ask it to rm -rf build mid-session and watch
the command get stopped.
See ARCHITECTURE.md for the full design and research/ for the platform reference, the comparison, and the threat model.
- You are in Goal Mode when the sidebar shows the goal banner. Goal Mode is the
goalagent plus its guard; the live banner — objective, todos, review status — in the TUI sidebar is the persistent indicator that it is active. Keep the sidebar open: OpenCode's status bar does not expose a per-agent mode label, so the sidebar banner is the canonical signal. - It will not claim done until the gates pass. After it implements and verifies, the
guard runs the review gates; a premature
Goal Completedis rewritten to a visible blocked marker until every required gate passes. - Blocked commands explain what and why. When the guard stops a destructive command
it names both the offending command and the reason, so you can adjust rather than
guess. Tune this behavior with
blockDestructiveandtoastOnBlock(see below), or turn it off entirely with YOLO mode.
Goal Mode works with zero configuration. When you want to tune it, set options in
opencode.json or through GOAL_GUARD_* environment variables. The plugin is
referenced by its installed path, which is how OpenCode passes it options:
| Option / env | Default | Effect |
|---|---|---|
blockDestructive / GOAL_GUARD_BLOCK_DESTRUCTIVE |
true |
Block destructive bash before execution. |
blockNetworkExec / GOAL_GUARD_BLOCK_NETWORK_EXEC |
true |
Block curl | sh-style remote execution. |
enforceCompletion / GOAL_GUARD_ENFORCE_COMPLETION |
true |
Rewrite a premature Goal Completed. |
autoContinue / GOAL_GUARD_AUTO_CONTINUE |
true |
Auto-continue an idle goal that is not complete yet. |
maxAutoContinue / GOAL_GUARD_MAX_AUTO_CONTINUE |
50 |
Hard cap on automatic continuations per goal session. |
programmaticReview / GOAL_GUARD_PROGRAMMATIC_REVIEW |
true |
Have the guard launch the required reviewers itself on idle (as subtasks on the goal session). |
reviewTimeoutMs / GOAL_GUARD_REVIEW_TIMEOUT_MS |
360000 |
Per-reviewer wall-clock cap (ms) for a programmatic review. |
reviewPollMs / GOAL_GUARD_REVIEW_POLL_MS |
2500 |
Poll cadence (ms) while waiting for a reviewer's verdict. |
reviewIdleDeferMs / GOAL_GUARD_REVIEW_IDLE_DEFER_MS |
1500 |
Delay (ms) after idle before launching reviewers (lets the host finish the idle transition so promptAsync is not rejected as SessionBusy). |
reviewIdleRetryMs / GOAL_GUARD_REVIEW_IDLE_RETRY_MS |
2500 |
Backoff (ms) between automatic retries when the host is still busy after idle. |
maxReviewIdleRetries / GOAL_GUARD_MAX_REVIEW_IDLE_RETRIES |
10 |
Max automatic idle-review retries before pausing for manual review. |
maxReviewCycles / GOAL_GUARD_MAX_REVIEW_CYCLES |
12 |
Hard cap on programmatic review runs per goal; on reaching it the guard pauses for you. |
abortGraceMs / GOAL_GUARD_ABORT_GRACE_MS |
1200 |
Grace (ms) before an idle goal auto-continues, so a user cancel is always honored. |
injectSystemState / GOAL_GUARD_INJECT_SYSTEM_STATE |
true |
Inject live guard state into the prompt. |
persist / GOAL_GUARD_PERSIST |
true |
Persist state under the XDG state directory. |
contextualGates / GOAL_GUARD_CONTEXTUAL_GATES |
true |
Require specialist gates by goal keywords and changed files. |
requireCodeReview / GOAL_GUARD_REQUIRE_CODE_REVIEW |
auto |
When to require the code-only diff and verification gates: auto (only once the goal edits a file), always, or never. Lets a non-code agentic goal complete on its evidence. |
restrictSubagents / GOAL_GUARD_RESTRICT_SUBAGENTS |
true |
Lock the goal-* subagents to the Goal agent. |
maxSessions / GOAL_GUARD_MAX_SESSIONS |
200 |
Session cache size. |
sessionTtlMs / GOAL_GUARD_SESSION_TTL_MS |
86400000 |
Idle session TTL (ms). |
toastOnBlock / GOAL_GUARD_TOAST_ON_BLOCK |
true |
Toast when something is blocked. |
toastOnReview / GOAL_GUARD_TOAST_ON_REVIEW |
true |
Toast on each review verdict and when completion unlocks. |
sidebarBanner / GOAL_GUARD_SIDEBAR_BANNER |
true |
Show the live Goal todo section in the TUI sidebar. |
sidebarColor / GOAL_GUARD_SIDEBAR_COLOR |
#FFD700 |
Color of the GOAL label for a running goal. |
sidebarDoneColor / GOAL_GUARD_SIDEBAR_DONE_COLOR |
#FF5555 |
Color of a done goal in the sidebar. |
sidebarMutedColor / GOAL_GUARD_SIDEBAR_MUTED_COLOR |
#808080 |
Color for pending Goal todo rows while a goal is running. |
completionMarker / GOAL_GUARD_COMPLETION_MARKER |
Goal Completed |
Phrase that, at the start of a message, claims completion. |
blockedMarker / GOAL_GUARD_BLOCKED_MARKER |
Goal Not Completed |
Replacement written when a completion claim is blocked. |
yolo / GOAL_GUARD_YOLO |
false |
YOLO mode. Relax the soft gates — network-exec blocking, completion enforcement, the Goal-only subagent lock, and block toasts. Destructive guarding stays on unless allowDestructive is also set. Any key you set explicitly still wins. |
allowDestructive / GOAL_GUARD_ALLOW_DESTRUCTIVE |
false |
Turn off destructive-command guarding. With yolo: true this is full YOLO — nothing is blocked. Works on its own as well. Use with care. |
allowCommands / GOAL_GUARD_ALLOW_COMMANDS |
[] |
Allow-list: a bash command matching any of these JavaScript regular expressions is never blocked. Array, or a comma- or newline-separated string for the env var. |
extraDestructive / GOAL_GUARD_EXTRA_DESTRUCTIVE |
[] |
Deny-list: a bash command matching any of these JavaScript regular expressions is treated as destructive, extending the built-in analyzer. |
Every gate is individually tunable, and YOLO mode is the one-switch escape hatch:
// opencode.json — never blocks anything (full rights):
["./plugins/goal-guard.js", { "yolo": true, "allowDestructive": true }]# Or via environment, for a throwaway sandbox:
GOAL_GUARD_YOLO=1 GOAL_GUARD_ALLOW_DESTRUCTIVE=1 opencodeyolo: truealone removes completion gating, the subagent lock, network-exec blocking, and toasts — but a destructiverm -rf /is still stopped.- Add
allowDestructive: trueand that last guard drops too: full YOLO. - For surgical control, leave YOLO off and use
allowCommandsto wave specific commands through, orextraDestructiveto block additional ones, for example{ "allowCommands": ["^docker compose ", "^rm -rf \\./tmp/"] }.
YOLO only relaxes keys you did not set explicitly, so a per-key option always wins.
Do not guess — use the tool. goal-config (installed as
opencode-goal-mode-config, or node scripts/goal-config.mjs from the repository)
lists every key, explains how to set one, ships paste-ready recipes, and previews the
resolved configuration:
opencode-goal-mode-config list # every key: default, env var, effect
opencode-goal-mode-config recipe full-yolo # a paste-ready opencode.json snippet
opencode-goal-mode-config effective '{"yolo":true}' --diff # confirm what it resolves toThe customization skill and the /goal-mode-customize command, both installed
alongside the plugin, walk the agent through a discover, apply, then verify loop built
on this tool.
Slash commands: /goal, /goal-contract, /goal-review, /goal-evidence,
/goal-evidence-map, /goal-status, /goal-repair, /goal-reset, /goal-final,
/goal-mode-customize.
Tools the model can call: goal_contract, goal_evidence, goal_evidence_map,
goal_reviewer_memory, goal_status, goal_reset.
opencode agent listdoes not showgoal. The agents did not land where OpenCode reads them — re-runopencode-goal-mode --globaland restart OpenCode.- No sidebar todo section. TUI plugins load from
tui.json, not theplugins/directory. Confirm~/.config/opencode/tui.jsonlistsopencode-goal-mode, then fully restart OpenCode. The sidebar is experimental and only appears inside a Goal session with a goal set; enforcement works regardless of the sidebar. - Reviews did not start on their own. After you stop with work done, the guard retries automatically while the session is still busy, so you should not need to type "continue". Reviewer subtasks launch on the goal session, and the guard starts the next assistant turn with fixes or completion rather than as a synthetic user message.
- The build agent is blocked by the shell guard. The shell guard only polices Goal Mode agents (agents whose id starts with
goal-, plus the primarygoalagent). Non-Goal agents likebuild,plan,explore, andscoutare not policed — they bypass the guard automatically. If a Goal Mode agent is being blocked, use the globalallowCommandsorextraDestructiveoptions to tune the classification, oryolo/allowDestructiveto relax guarding entirely. - The explorer subagent prompts on basic shell commands. Read-only commands such as
grep,cat, andsed -nare pre-approved ongoal-explorer. - The goal agent stalls waiting on a question. The primary
goalagent setsquestion: deny; it records assumptions in the Goal Contract and keeps working instead of pausing. - Goal Mode stopped after switching agents. Switching the session off the
goalagent — to Build or Plan, or through an action that cycles the agent — intentionally pauses Goal Mode; the guard shows a toast and stops treating that turn as a goal. While paused, no Goal activity fires on that session: the sidebar hides, the completion claim is no longer rewritten, auto-continue sends no "keep going" prompt, and programmatic reviews are not launched (so the guard will never switch your main agent back togoalon its own — see issue #428). Your contract, reviews, and evidence are preserved. Switch back to thegoalagent, or run/goal, to resume with all state intact and reviews will run again on the next idle. - A safe command was blocked. Inspect how the analyzer reads it with
node benchmarks/external.mjs --json, allow it for that project withallowCommands, and please open an issue.
- Requirements. Node 20.11 or newer, OpenCode configured to load local agents,
commands, and plugins (tested against
@opencode-ai/plugin1.17.6 and compatible with the 1.15-and-later hook surface), and a working provider and model. Agents inherit your OpenCode default model. - Safety. The installer copies
agents/,commands/,skills/, andplugins/, merge-registers the sidebar intui.json, and writes a manifest. It never touches auth files, tokens, or provider configuration. The guard is a guardrail, not a sandbox, and fails closed on a parser error while failing open on genuinely unanalyzable input; see SECURITY.md for the threat model and a private reporting channel.
Contributions are welcome. CONTRIBUTING.md covers the development
loop and release process, and CHANGELOG.md records the full history.
Releases are automated and version-synced: a single pushed vX.Y.Z tag runs the CI
gate, publishes to npm, and creates the matching GitHub Release.

{ "plugin": [ ["./plugins/goal-guard.js", { "blockDestructive": true, "contextualGates": true }] ] }