@mcrescenzo/opencode-goalsopencode plugin for defining, tracking, and evaluating session goals via a /goal command and hidden evaluator agents.
0
35
6 in 7 days
29.6
Multi-signal model
6 days ago
2026-08-13
Install and configure
opencode.jsonWrites to this project's opencode.json — applies to this repository only.
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@mcrescenzo/opencode-goals@0.1.1"]
}Writes to ~/.config/opencode/opencode.json — applies to every project.
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@mcrescenzo/opencode-goals@0.1.1"]
}If you want to modify the plugin locally, install it into the project and reference the local path.
shell
pnpm add -D @mcrescenzo/opencode-goalsopencode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.
Long agentic sessions drift: the assistant wanders off the original ask, loses
the thread after a few tool calls, or declares victory ("Done! ✅") on work
that doesn't actually satisfy what you asked for. /goal fixes that by
keeping a single, persistent objective for the session and refusing to accept
"I'm done" at face value.
Under the hood, after each assistant turn the plugin relays bounded, sanitized
evidence — a recent transcript excerpt, tool call/result summaries, the
session diff, the goal text, and any assistant-claimed evidence — to a hidden
mcrescenzo-opencode-goals-evaluator-v1 agent, which returns the real,
final verdict. For file/test/docs/review-oriented goals a hidden read-only
mcrescenzo-opencode-goals-researcher-v1 agent may gather additional
evidence first. Until the evaluator marks the goal
met (or the goal is blocked, paused, or its budget is exhausted), the plugin
sends an auto-continue prompt so work proceeds without a human re-prompting
every turn.
Example
/goal Get all tests in src/api passing and update the CHANGELOG
Setting the goal fires an immediate toast and puts the session to work:
Goal active
Goal: Get all tests in src/api passing and update the CHANGELOG
Status: active · 0/100 turns · 0s
Evaluator: waiting for first verdict.
While the assistant works, ambient status toasts reflect the evaluator's running verdict — for example, if it isn't convinced yet:
Goal: Get all tests in src/api passing and update the CHANGELOG
Status: active · 3/100 turns · 1m 12s
Evaluator: not met (medium confidence): No evidence yet that src/api tests
pass or that CHANGELOG was updated.
Gap: No test run output for src/api
Under the hood, the evaluator's real verdicts are structured JSON matching the
schema in goals-core.js (illustrative values):
{
"met": false,
"confidence": "medium",
"evidence_gaps": ["No test run output for src/api"],
"criteria": [
{ "description": "All tests in src/api pass", "status": "unverified", "evidence_ref": "" }
],
"next_steps": ["Run the test suite for src/api"],
"reason": "No evidence yet that src/api tests pass or that CHANGELOG was updated.",
"next": "continue"
}
Once the evaluator sees real evidence in the transcript (a passing test run,
an updated CHANGELOG.md), it flips met to true and the goal toast changes
accordingly:
Goal achieved
Goal: Get all tests in src/api passing and update the CHANGELOG
Status: achieved · 7/100 turns · 3m 41s
Evidence: All tests in src/api pass and CHANGELOG.md has a new entry.
Only the hidden evaluator can produce that final "achieved" state — the build assistant claiming completion in its own reply is never enough on its own.
Install and registration
The package is published on npm as @mcrescenzo/opencode-goals (registry
version 0.1.1 for this release). OpenCode installs configured npm plugins and
their dependencies into its own cache, so register the package name rather than
adding it to the workspace application. See
OpenCode's plugin documentation for the
host's package and local-directory discovery contract.
This package declares @opencode-ai/plugin as its direct runtime dependency to
pin the host contract. @opencode-ai/sdk@1.17.14 is a direct development-only
dependency for generated-contract and live-host tests; shipped source does not
import it. The runtime dependency's own packages are transitive dependencies,
recorded in the repo-only DEPENDENCY-LICENSES.md inventory.
Exact declared ranges, verified Node/SDK/host cells, beta boundaries, and the
difference between generated, mocked, injected-client, live-host, and packed
evidence are maintained in docs/COMPATIBILITY.md.
OpenCode supports the following registration and discovery paths:
Published npm package
Add the published package name to the "plugin" array in opencode.json:
{
"plugin": [
"@mcrescenzo/opencode-goals"
]
}
OpenCode resolves npm plugin packages at startup. The package's public registry version was verified separately from the local manifest during release checks; the packed-host smoke exercises the exact local tarball without treating that as a registry-install result.
Project-local source checkout
OpenCode automatically loads JavaScript/TypeScript plugin files from
.opencode/plugins/ in the project. For local Goals development, clone this
repository under vendor/opencode-goals, install its dependencies there, and
create .opencode/plugins/goals.js with this loader:
export { GoalPlugin } from "../../vendor/opencode-goals/goals.js";
Adjust the loader import only if your checkout lives elsewhere; do not add the
source-file path to the opencode.json package array. Arbitrary path entries are
not part of this release's verified stable-host installation contract.
Global local-plugin directory
For a local checkout shared across projects, place an equivalent loader file in
~/.config/opencode/plugins/. Imports are relative to that loader; for example,
if the checkout is ~/.config/opencode/src/opencode-goals, use:
export { GoalPlugin } from "../src/opencode-goals/goals.js";
The credential-free packed host smoke exercises loader discovery from both the project and isolated-global plugin directories. It does not modify your actual global configuration.
Restart OpenCode after adding or changing any package entry, loader, plugin source, or bundled command documentation. Already-running processes and sessions keep the plugins, commands, and agents loaded at startup.
If /goal is not available, confirm either that opencode.json contains
the package name or that the loader is a direct .js/.ts child of the correct
project/global plugin directory. Then restart OpenCode and check startup output
for plugin import or dependency errors.
What it does
/goal <objective> sets one session goal. Key behaviors:
- Evaluator has final say. Assistant-authored completion markers are signals, not authority — see "For AI agents" below for the exact marker contract. Only the hidden evaluator marks a goal achieved.
- Two hidden agents, locked down.
mcrescenzo-opencode-goals-evaluator-v1runs with all tools denied;mcrescenzo-opencode-goals-researcher-v1is deny-by-default with only targetedreadandgrepallowed under secret-path deny rules (.env,*.pem,*.key, common credential stores, and every descendant ofsecrets/credentialsdirectories). Sensitive parents override safe-example allowances. Broadglob,list, andlspenumeration is denied. Both inherit the session's configured model. The genericgoal-evaluatorandgoal-researchernames are temporary compatibility aliases in this release and are never internal fallback targets. - Human-first pausing. A real user message after the last auto-continuation pauses before any hidden agent or further continuation, so the latest human instruction wins. Permission/question prompts block automation while waiting; a rejection pauses the goal.
- Neutral automation admission. Before hidden evaluation and again before
each autonomous continuation, Goals joins the root session's durable
.opencode/automation-admission/v1/lease. Conforming plugins therefore serialize prompt injection across duplicate factories and processes, while malformed state, unknown status, pending interactions, or contention deny the attempt. Admitted control parts carry structuralmetadata.automation.protocolprovenance and are excluded from human and independent-evidence classification. - Fail-closed continuation settlement. A thrown or returned send error, an
acceptance timeout, or any unknown admission outcome pauses the goal after
one attempt. The history and toast explain that acceptance was not confirmed;
inspect the session and run
/goal resumeexplicitly. Goals never retries an uncertain send automatically because the first request may have been accepted. - Runaway backstops. Beyond the per-goal turn budget there are lifetime
ceilings (a ~3-hour wall-clock cap and a hidden-call budget) and stall
heuristics that pause repeated low-progress, no-tool-call, or repeated-diff
loops with no criteria progress. These are not reset by
/goal resume. - Verification and observe modes. A
--verifydirective tells the build agent which command to run under its normal permissions (the plugin never executes it), and--observelets the evaluator report not-met verdicts without auto-continuing. - Polite progress toasts. The active session goal emits compact lifecycle toasts plus a best-effort heartbeat while active, including the objective, turn usage, latest evaluator reason, verification failure, or error summary (see the Example above).
- Persistence + recovery. Goal state is written per workspace and active goals recover as paused after an opencode restart (see Persistence below).
- Scoped lifecycle cleanup. Duplicate plugin factories share a reference-counted workspace runtime. Disposing one leaves the surviving factory's work intact; disposing the final reference cancels only that workspace's transient controllers, heartbeat, diagnostics, and queues. Durable goal files are left in place for paused recovery.
The plugin self-registers the /goal command from its bundled commands/goal.md; no separate command file is required.
It intercepts a command only while the final live cfg.command.goal definition is
exactly the bundled definition. An equivalent duplicate package/path
registration is shared safely; a foreign or legacy parent command is preserved,
diagnosed, and not intercepted. Remove the separate parent /goal definition
and restart OpenCode to migrate.
Privacy: hidden evaluator/researcher calls send transcript excerpts, bounded tool summaries, diffs, goal text, and research summaries to the configured model/provider. Secret-path protections redact file/diff/research content for sensitive paths, and transcript text receives best-effort inline credential pattern redaction before hidden-agent relay, including common key assignments, provider token prefixes, bearer/basic auth, cookies, URL credentials, PEM keys, AWS access keys, JWT-shaped strings, and session/csrf-style token prefixes. This is not a comprehensive secrecy boundary: arbitrary opaque secrets may not match these patterns, do not paste secrets into chat, and nested
.gitignorefiles are not a secrecy boundary for transcript evidence.
For AI agents
If you're the build assistant working under an active /goal, signal
progress with markers the hidden evaluator treats as claims, not proof: put
[goal:evidence] <proof> immediately before a terminal [goal:complete], and
state the concrete blocker on the line immediately before [goal:blocked]
when you genuinely need human input. The evaluator — not these markers —
decides whether the goal is actually met, so [goal:complete] without a
preceding [goal:evidence] line is rejected.
Commands
/goal <objective>— start or replace the session goal./goal status— objective, criteria, constraints, turn usage, evaluator reason, evidence, blocker, and recent history./goal help,/goal --help,/goal -h— usage summary for the command surface and completion marker./goal history— lifecycle events for the current session goal./goal pause,/goal resume,/goal clear— control automation./goal observe,/goal observe on,/goal observe off— toggle observe mode, where hidden evaluation/research still runs but not-met verdicts pause instead of auto-continuing./goal continueor/goal step— send one explicit continuation, useful after an observe-mode verdict pause./goal edit <new objective>— revise the objective while preserving the turn limit and history; omitted success, constraints, and verify flags are cleared.- Clear aliases:
clear,stop,off,reset,none,cancel.
Configuration options
Most configuration is per goal, supplied as flags when setting a goal. Value
flags accept both --flag value and --flag="multi word value" forms; boolean
flags use bare or inline forms. Use -- before objective text that contains
literal --flag tokens:
--max-turns <n>— auto-continue turn budget. Default: 100.--success "criteria"— explicit success criteria the evaluator must verify.--constraints "constraints"/--non-goals "constraints"— constraints and non-goals to honor.--verify "command"— frozen build-agent verification directive. The plugin does not execute this command; it injects the directive into goal context and the evaluator treats transcript-visible results as authoritative evidence.--observe— hidden evaluation/research still runs and is budgeted, but not-met verdicts pause with the verdict instead of sending auto-continuations. Use bare--observeto enable it, or inline boolean forms such as--observe=off; accepted boolean tokens aretrue/false,on/off,yes/no, and1/0.
Notable defaults and safety limits (constants in goals-core.js, not currently
user-configurable):
- Default auto-continue budget: 100 turns.
- Minimum delay between auto-continues: ~1s.
- Per-goal lifetime wall-clock cap: ~3 hours (a runaway backstop, not reset
by
/goal resume; a fresh/goalor/goal editstarts it over). - Hidden-call budget scaled to the turn budget, plus stall heuristics that pause after repeated low-progress or no-tool-call continuation turns.
The canonical hidden evaluator and researcher agents default to the session's
configured model (cfg.model) — no model ID is hard-coded. At runtime,
evaluator, researcher, and audit prompts follow the model captured from the
latest genuine build turn (falling back to the initial build model), so their
quality and cost track that session-model choice.
The skeptical audit is a separate judgment pass, but it uses the same selected model and reviews the same evidence plus the primary verdict. It therefore adds skeptical prompting, not cross-model or evidence independence. No comparative benchmark currently supports claiming that a stronger model materially reduces false positives, so the plugin does not impose a separate evaluator-model override.
Hidden model calls and cost
An ordinary not-met cycle uses 1 hidden call (evaluator); a met cycle uses
2 (evaluator + skeptical audit); and an evidence-seeking cycle that runs the
researcher and then reaches met uses 4 (evaluator + researcher + evaluator +
audit). The audit is the extra call used by the plugin's fail-closed completion
check, not optional overhead in a met cycle. Each askGoalEvaluator pass can
retry once for malformed JSON or protocol confusion; the audit does not retry.
The respective retry-inclusive maxima are 2 / 3 / 6 calls.
The user-facing controls are --max-turns (default 100), the ~3-hour active
lifetime cap, and a hidden-call cap of 4 × maxTurns + 20. These bound calls and
lifetime, not provider dollars or tokens: actual cost varies with the model,
context, output, and researcher tool steps.
Persistence
Goal state is written per workspace:
.opencode/goals/state.json
.opencode/goals/state.json.ledger.jsonl
.opencode/goals/cycles.jsonl
Cross-plugin automation admission uses a separate neutral coordination tree:
.opencode/automation-admission/v1/
Records are keyed per session, while these three files are shared by every session in the workspace. Serialized read-merge-write persistence preserves concurrent sessions. The in-memory map tracks at most 256 sessions, evicting the oldest non-active entry first; if all 256 are active, adding another pauses and evicts the oldest active goal while leaving its persisted state recoverable. Cleared session IDs are tombstoned for seven days to prevent stale resurrection.
Active goals recover as paused after an opencode restart; run /goal resume
to continue with fresh turn and stall counters. State writes are refused when the
goals directory or state/ledger/cycle-ledger path is an existing symlink or escapes the
project directory. The cycle ledger stores bounded structured evaluator records
(verdict, criteria, diff fingerprint, verify evidence, and audit context) for
incremental criteria stability and stuck-loop detection. Keep .opencode/goals/ out of git unless a project
intentionally wants local goal history committed.
Plugin diagnostics are written outside the project, under
$OPENCODE_PLUGIN_DIAGNOSTICS_DIR (or $XDG_STATE_HOME/opencode/plugin-diagnostics/),
as one goals-<date>-<pid>.jsonl per process and day. Each file rotates once at
5 MB, and the directory is swept on startup: files older than 14 days, and any
beyond the 20 most recent, are deleted. The current run's file is never pruned.
Admission is root-session scoped, bounded, fail-closed, and ignored by git via
its own exact .gitignore. It does not replace the host scheduler or make human
and automation admission atomic: the public host API cannot transact across the
final idle/pending-interaction snapshot and prompt acceptance. Goals holds the
neutral scope lock through the host's short acceptance call, diagnoses this
residual host race, and applies any subsequently observed interaction before
another reservation can succeed. Where the host cannot list outstanding
interactions, the durable event-derived set is the minimum check rather than a
claim of stronger assurance. See
docs/AUTOMATION-ADMISSION-PROTOCOL.md
for the interoperable record and provenance contract.
Running the tests
node --test tests/*.test.mjs
This is also wired as the package's test script, so bun run test (or
npm test) runs the same command. See CONTRIBUTING.md for
the full test suite breakdown (packed-tarball release smoke, zero-token
runtime registration smoke, and the opt-in host-contract smoke), development
setup, and pull request expectations.
Support and security
Compatibility claims are limited to the exact cells in
docs/COMPATIBILITY.md; the declared package range is
not a promise that every OpenCode 1.x host has been tested.
Use GitHub Issues for public bugs, support requests, and feature proposals:
https://github.com/mcrescenzo/opencode-goals/issues
Do not post secrets, credentials, private logs, exploit details, sensitive
vulnerability details, or private workspace data in public issues. See
SECURITY.md for the current reporting policy, CONTRIBUTING.md for pull
request expectations, and CHANGELOG.md for release notes.
Development notes
See AGENTS.md in this directory for contributor invariants (SDK contract direction and version compatibility, the hooks the plugin wires, hidden-agent behavior, and testing expectations).