Skip to content

devinoldenburg/opencode-goal-mode

OpenCode Goal Mode

The OpenCode agent that cannot fake "done" — and cannot wreck your repository getting there.

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 version npm downloads CI Release license

npm install -g opencode-goal-mode

Most 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.

The pitch in one minute

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 goal agent 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.

Watch it refuse to lie

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-auditor

The 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.

OpenCode Goal Mode sidebar preview

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.

Why you will want it

"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.

The numbers

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%

Guard accuracy on real third-party commands

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.

Per-command analysis latency

How it compares

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)

Mechanically enforced goal discipline versus Claude Code and Codex

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.

Install

One command. Requires Node 20.11 or newer and OpenCode. Supported on macOS and Linux:

npm install -g opencode-goal-mode

Then 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.

Quick start

# 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.

What the first run looks like

  • You are in Goal Mode when the sidebar shows the goal banner. Goal Mode is the goal agent 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 Completed is 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 blockDestructive and toastOnBlock (see below), or turn it off entirely with YOLO mode.

Configure it (or do not)

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:

{
  "plugin": [
    ["./plugins/goal-guard.js", { "blockDestructive": true, "contextualGates": true }]
  ]
}
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.

YOLO mode and per-command rules

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 opencode
  • yolo: true alone removes completion gating, the subagent lock, network-exec blocking, and toasts — but a destructive rm -rf / is still stopped.
  • Add allowDestructive: true and that last guard drops too: full YOLO.
  • For surgical control, leave YOLO off and use allowCommands to wave specific commands through, or extraDestructive to 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 to

The 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.

Troubleshooting

  • opencode agent list does not show goal. The agents did not land where OpenCode reads them — re-run opencode-goal-mode --global and restart OpenCode.
  • No sidebar todo section. TUI plugins load from tui.json, not the plugins/ directory. Confirm ~/.config/opencode/tui.json lists opencode-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 primary goal agent). Non-Goal agents like build, plan, explore, and scout are not policed — they bypass the guard automatically. If a Goal Mode agent is being blocked, use the global allowCommands or extraDestructive options to tune the classification, or yolo / allowDestructive to relax guarding entirely.
  • The explorer subagent prompts on basic shell commands. Read-only commands such as grep, cat, and sed -n are pre-approved on goal-explorer.
  • The goal agent stalls waiting on a question. The primary goal agent sets question: 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 goal agent — 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 to goal on its own — see issue #428). Your contract, reviews, and evidence are preserved. Switch back to the goal agent, 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 with allowCommands, and please open an issue.

Good to know

  • Requirements. Node 20.11 or newer, OpenCode configured to load local agents, commands, and plugins (tested against @opencode-ai/plugin 1.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/, and plugins/, merge-registers the sidebar in tui.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.

Contributing

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.

License

MIT. Built for OpenCode.

About

Strict Goal Mode for OpenCode: a goal agent + enforced review gates + a guard plugin that blocks destructive commands and premature "Goal Completed", with a live TUI goal banner.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors