Open-source project
willytop8/OpenCode-goal-plugin avatar
willytop8/OpenCode-goal-plugin

The OpenCode goal plugin keeps working after compaction, and every compatibility row ends in a warning

Durable, guarded goal workflows for OpenCode with persistence, safety limits, agent tools, and evidence-gated completion.

283 stars25 forksJavaScriptMIT

At a glance

What is it?
A session-scoped /goal workflow that holds a goal in context, resumes the session when the model goes idle, and refuses to call a task complete without evidence. Its own compatibility matrix is the most useful part, because every row carries a caveat about command output being routed through the model rather than rendered.
Who is it for?
Use this plugin if you want an unattended OpenCode session to keep working toward one stated objective and you want the stopping conditions written down before it starts, because the five limits and the blocker path are the reason to install it. Do not adopt it expecting your status command to print a report, since command output goes through the model and the compatibility table marks that a warning in every row.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • willytop8/OpenCode-goal-plugin283 ★ · +37
Star history
Repository2026-08-222026-10-22
willytop8/OpenCode-goal-plugin246283

37 Stars in the last 61 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

A goal stays in context across turns and across compaction

The mechanism is narrower than it first sounds, and that is the point. You set a goal for the session, and the plugin keeps it in context rather than relying on you to restate it, including across turns and across compaction, which is the moment when a long unattended session would otherwise forget what it was doing. When the assistant goes idle, the plugin auto-continues the session instead of waiting for you to type again. It stops under exactly three conditions: the goal is marked complete, a blocker is reported, or a safety limit is reached. So the loop is not open-ended. There is a status model underneath it with three explicit values, `active`, `paused` and `blocked`, and lifecycle notices fire only on transitions, which is what keeps a long session from filling your context with status noise. Restart recovery is project-local, backed by persisted state and a bounded lifecycle ledger, so a session that dies can be picked up rather than restarted from nothing.

Five limits guard the loop, and a blocker is a first-class stopping state

The guarded part is specified as a list, and the list is the feature. Auto-continuation is bounded by turn, duration, token, no-progress and no-tool-call limits, so there are three different kinds of runaway to catch. Duration and turns bound the obvious case of a session that simply will not end. The token limit bounds total spend, which matters because every idle continuation is another billed call. Then there are two limits that only make sense once you look at what the model is doing: a no-progress limit, for a session that keeps replying without advancing, and a no-tool-call limit, for a session that is talking rather than acting. Each of those catches a failure mode that a turn counter alone would miss. A blocker is handled separately and is not a limit at all, it is a state the model can report, which is the escape hatch for a goal that cannot be finished. Multiple goals and ordered goal sequences are supported, so a blocker on one does not have to end the session.

Completion needs evidence, and the verifier is independent and fail-closed

The claim of done is gated rather than trusted. Completion is evidence-gated, with an optional independent verifier, and the two properties that matter are independence and fail-closed behaviour. Independent means the verifier is a separate agent, not the same one that did the work asking itself whether it succeeded, and the two are made collision-safe so a goal agent and a verifier agent cannot overwrite each other. Fail-closed means a verification that cannot be completed does not pass, so an error in the verifier produces a stopped session rather than a false success. The compatibility evidence shows this working rather than just being claimed. In one live-provider row the model claimed completion with a bare marker and no evidence, was rejected, self-corrected and then completed cleanly. In the OpenCode 2 verification list, foreign-verifier rejection appears as a distinct check, alongside restricted independent completion auditing. The claims are structured, with an evidence marker and a completion marker, rather than a sentence parsed out of prose.

Every compatibility row ends in a warning, because command output is routed through the model

Look at the last column of the version matrix and it is a warning in every single row, which tells you more than the checkmarks do. The historical custom-command presentation entry, recorded at v0.6.6, is either not displayed, or command text routed to model with the mutation guard verified, or command text routed to model on its own. The explanation is in the notes. In that release OpenCode 1.17.15 retained the original command-parts array, so assigning a new output parts array did not replace the raw command argument sent to the model, and the current implementation mutates that retained array in place so the plugin-generated result becomes the prompt for the turn. But custom commands still run through the model rather than rendering hook output directly, so the visible response may summarize or paraphrase the result. The practical consequence is that you cannot rely on `/goal status` printing a status report, and the project is upfront about it rather than letting you discover it during an unattended run.

Plan mode is a stop condition, and 0.10.0 fixed the case where it was not

Two of the fixes in the newest release are about the same subsystem, and the compatibility evidence pins them down precisely. The 0.10.0 release notes cover a plan guard and title fixes, and the deterministic canary reproduced both defects on two separate OpenCode builds: a first command Plan goal not being held, and a stale running title that persisted after completion. What a hold means is visible in the live matrix. On one row, auto-continue is recorded as held correctly under the Plan agent, with `stopped: true` and zero auto-continues. So while the model is in plan mode the loop does not resume it, which is the difference between a guard that exists and a guard that is wired in. The stale title is the smaller half of the same release and the more visible one, since a session that has finished still looking busy is exactly the state an operator checking on it would misread. Both were caught by a canary running against a real serve process, not by a mocked test.

Two export paths point at one file, and the bin runs the verifier

The package manifest has two details worth reading before you install it. The export map offers a root entry and a `./server` entry, and both of them resolve to the same file, the main entry point, with the same type declarations. So `./server` is an alias rather than a second surface, and importing it does not get you a different module or a server-specific build. The second detail is the `bin` field, which maps the executable name `opencode-goal-plugin` to a verification script rather than to the plugin. That means the command you would find on your path after installing the package runs a check, not the workflow. Neither of these is a defect, and both are the kind of thing that takes a debugging session to work out if you assume the names describe what you expect. The published file list is broader than the runtime surface, including the sources, the examples, the demo, the docs and the repository meta files, so the tarball carries the documentation with the code.

The install examples pin a version ahead of the newest release tag

OpenCode installs npm plugins itself from your configuration, so there is nothing to install by hand, and the plugin is added to `opencode.json`, either the user config under the OpenCode config directory or a project-local one, with a pinned version. On OpenCode 2 the plugin registers its own command, so the config is a single line:

json
{
  "plugins": ["[email protected]"]
}

On OpenCode 1 the same plugin also needs its goal command declared, and the declaration is what tells the host which agent runs it:

json
{
  "plugin": ["[email protected]"],
  "command": {
    "goal": {
      "description": "Set a session-scoped goal and auto-continue until complete.",
      "template": "$ARGUMENTS",
      "agent": "build"
    }
  }
}

The version in both examples is 0.11.0, and the manifest agrees, while the three most recent release tags are 0.10.0, 0.9.0 and 0.7.0. So the documented pin is ahead of the newest tag, and 0.8.0 does not appear between 0.7.0 and 0.9.0. If you follow the example literally you are installing a version whose release you cannot read the notes for, which is a reasonable thing to notice before leaving it running unattended.

A leftover version 1 command block shadows the plugin on version 2

There is a migration trap, and it is the kind that produces a confusing symptom rather than an error. A version 1 config keeps working on OpenCode 2, because the `plugin` key is normalised to `plugins`. But the `command.goal` entry from that config is registered after the plugin's own commands on version 2, which means it would shadow the plugin handler rather than being ignored. The guidance is to remove it. The plugin then detects the conflict and re-asserts its own registration, so the failure mode is not that the plugin stops working, it is that something else is answering. For anyone who has been running this on version 1 and upgrades the host, the symptom to watch for is the command behaving like a plain custom command, sending the raw arguments to the model and doing nothing else, which looks exactly like the plugin not being installed. The compatibility table is explicit that both are checked, plugin load and the command itself, against OpenCode 2.0.16.

Editorial conclusion

Use this plugin if you want an unattended OpenCode session to keep working toward one stated objective and you want the stopping conditions written down before it starts, because the five limits and the blocker path are the reason to install it. Do not adopt it expecting your status command to print a report, since command output goes through the model and the compatibility table marks that a warning in every row. Before you leave it running unattended, read the compatibility note, which says the plugin depends on experimental OpenCode hooks and should be re-tested against the exact build and provider you intend to use. Check the version you pin, because the install examples name a version ahead of the newest release tag, and remove a leftover version 1 command block from your config, since on version 2 it shadows the plugin handler.

Frequently asked questions

What does the opencode-goal-plugin do?

It adds a session-scoped /goal workflow to OpenCode. The goal stays in context across turns and compaction, the session auto-continues when the assistant goes idle, and it stops when the goal is complete, a blocker is reported, or a safety limit is reached.

How do I install the OpenCode goal plugin?

OpenCode installs npm plugins itself from your config, so there is nothing to npm install. Add a pinned version to opencode.json: on OpenCode 2 that is a plugins array alone, and on OpenCode 1 you also declare the goal command with its description, template and agent.

Does the OpenCode goal plugin work with OpenCode 2?

It is verified against OpenCode 2.0.16, covering plugin load, the /goal command, native agents, Plan holds, restricted independent completion auditing, foreign-verifier rejection and safe reload recovery. Remove any leftover command.goal entry from a version 1 config, because on version 2 it would shadow the plugin handler.

What stops the auto-continue loop in the goal plugin?

Turn, duration, token, no-progress and no-tool-call limits, plus a reported blocker and explicit completion. Status is one of active, paused or blocked, and lifecycle notices fire only on transitions. Restart recovery is project-local, backed by persisted state and a bounded lifecycle ledger.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/willytop8-opencode-goal-plugin.svg)](https://hysenlabs.com/projects/willytop8-opencode-goal-plugin)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/willytop8-opencode-goal-plugin.svg)](https://hysenlabs.com/projects/willytop8-opencode-goal-plugin)