Install · Update · Uninstall · Commands · Options · Troubleshooting
Releases · Changelog · Compatibility · Architecture · Issues · MIT license
OpenCode plugin for persistent goals, automatic continuation, token budgets, and evidence-based completion checks.
Latest release: v0.6.0 · Download archive
Target saves an objective outside the conversation history and starts another model turn while that
objective remains active. It tracks token usage and elapsed active time, supports requirement evidence
and explicitly requested verification commands, and stops on configured limits or repeated failures.
An animated TUI indicator shows the current state and token usage. Inspired by Codex /goal.
Completion is audited by the model using requirements and available evidence; Target does not guarantee that an objective will be achieved or automatically run every verification command.
Primary command:
/target <objective>
Short alias:
/t <objective>
A normal coding-agent request lives inside one model turn. Even a strong instruction such as “keep going until everything passes” can be forgotten after compaction, prematurely summarized, narrowed to an easier subproblem, or simply ended by the model.
Target separates the durable objective from conversation history:
User /target
|
v
Persistent Target state
|
+--> system policy
+--> token + time accounting
+--> verification contract
+--> circuit breakers
|
v
OpenCode turn
|
v
session.idle
|
+-- Target active? --> start a new physical turn --> repeat
|
+-- stopped? --------> stop
The model can end a turn. It cannot silently end an active Target.
Choose the language for the installer output:
Requires Node.js 22+ and a compatible OpenCode version; see the compatibility matrix.
Download opencode-target-0.6.0.tgz,
extract it, and open the extracted package directory. Run the installer there; v0.6.0 includes
local/global scope selection and standalone uninstallers in both languages.
sh install.shWindows:
powershell -ExecutionPolicy Bypass -File .\install.ps1Both installer and uninstaller offer Local (project) or Global (user) when launched without scope arguments.
Local files go into <project>/.opencode; global files go into the user OpenCode config directory.
The local menu asks for a project directory (default: the current directory, not the archive directory).
For unattended runs (quote paths containing spaces):
sh install.sh --local --project-dir /path/to/project
sh install.sh --global
sh uninstall.sh --local --project-dir /path/to/project
sh uninstall.sh --global.\install.ps1 -Local -ProjectDir "C:\Projects\my-project"
.\install.ps1 -Global
.\uninstall.ps1 -Local -ProjectDir "C:\Projects\my-project"
.\uninstall.ps1 -GlobalStop OpenCode, download and extract the newer archive, then rerun its installer with the same scope
and project directory. Both plugins are replaced. Existing configuration
options and saved Targets are preserved; the installer reports the previous and archive versions,
for example Updated OpenCode Target: 0.5.0 → 0.6.0. It installs the archive version without downloading another release.
Restart OpenCode after updating.
Stop OpenCode, then run sh uninstall.sh on Linux/macOS or
powershell -ExecutionPolicy Bypass -File .\uninstall.ps1 on Windows and select the installed scope.
For compatibility, you can also use sh install.sh --uninstall on Linux/macOS or
powershell -ExecutionPolicy Bypass -File .\install.ps1 -Uninstall on Windows. Russian installers
accept the same flags. Separate uninstall.sh / uninstall.ps1 and Russian variants are included.
Removal deletes both plugin files, Target registrations in the selected server/TUI configuration,
installation metadata, saved state, and empty plugin directories. Installer-created configs are
removed only if no user settings or comments remain. Repeated removal succeeds.
Other plugins and settings are preserved.
Local installations keep goals in <project>/.opencode/target/state; global installations retain
the existing system state directory, including legacy shared goals. Local removal never deletes
that legacy global store by default. Installed server and TUI plugins share the stored state path;
a local installation takes precedence over a global installation in its project.
Explicit state_dir settings and --state-dir / -StateDir are supported; deliberately sharing
a custom state directory also shares its deletion lifetime. Updates preserve the recorded state path.
The installer prints its progress in English. To use the Russian installer instead, run:
Linux/macOS:
sh install.ru.shWindows:
powershell -ExecutionPolicy Bypass -File .\install.ru.ps1The installer copies both the Target runtime and TUI indicator, then creates or updates the
TUI configuration automatically. No manual config edit or npm install is needed.
Restart OpenCode and start a new session after installation.
Existing JSON/JSONC settings, comments, and plugin options are preserved. Reinstalling updates the
files without duplicating the TUI entry and re-enables opencode-target-tui if it was disabled.
When both config files exist, registration is written to tui.jsonc (the higher-precedence file).
Malformed config is reported as an installation error and is not overwritten.
For global scope, the destination respects OPENCODE_CONFIG_DIR, then XDG_CONFIG_HOME, then
~/.config/opencode. OPENCODE_TUI_CONFIG selects a custom TUI config file when set; the plugin
path is made relative to that file. Project-specific overrides still follow OpenCode's precedence.
To install the current checkout with automatic scope selection, build the artifacts first:
npm ci
npm run build:localThen run the installer commands above. Manual copying below installs only the server plugin and
uses the global default state store unless you explicitly configure state_dir.
- Copy
dist/target.jsinto either:
~/.config/opencode/plugins/target.js
or, for one project:
.opencode/plugins/target.js
-
Restart OpenCode. Local plugin files are loaded at startup.
-
Start a new OpenCode session after installing or updating the plugin so the session receives the current tool list.
dist/target.js is self-contained except for the OpenCode-provided @opencode-ai/plugin package.
The repository also builds dist/target-tui.js, a separate OpenCode TUI plugin. It uses the
stock session_prompt_right slot (verified in OpenCode 1.18.34), beside the prompt controls:
│ ● Target: working · 16.6k. No Target-specific OpenCode build is required;
the compact counter is the current Target's spent tokens. Long reasons and budget totals stay
in /target status. Narrow terminals drop the status label before the counter, without wrapping.
It reads local state every 500 ms. OpenCode's animations_enabled controls animation.
The installer adds this TUI registration automatically. For manual installation, add it to
tui.json alongside your existing settings:
{
"$schema": "https://opencode.ai/tui.json",
"plugin": ["./tui-plugins/target.js"]
}The install scripts copy this artifact to <config-dir>/tui-plugins/target.js, where <config-dir>
is the selected project .opencode or user OpenCode directory, and register it in that scope's tui.json or
tui.jsonc. Existing settings, comments, and plugin entries are preserved; repeating the
installer is idempotent. Restart OpenCode after installation. The indicator is display-only;
lifecycle control remains /target pause, /target resume, and the other Target commands.
For a manual installation, copy dist/target-tui.js to the path used in tui.json. For one project,
use .opencode/tui-plugins/target.js and .opencode/tui.json with the same relative-path example.
For a locally installed package, use "plugin": ["opencode-target"] in tui.json; the package's
./tui entrypoint resolves to the generated indicator.
Automatic installations use the same stored state path for the server and indicator.
Manually copied plugins use the global default state directory and OPENCODE_TARGET_STATE_DIR.
If the server
uses an explicit state_dir, supply the same absolute directory to the TUI plugin:
{
"$schema": "https://opencode.ai/tui.json",
"plugin": [["./tui-plugins/target.js", { "state_dir": "D:/TargetState" }]]
}On Linux/macOS use an absolute path such as /home/me/.local/state/opencode-target. The indicator
uses local files and is intended for a local OpenCode instance. waiting means the Target is
active but the session is idle; it does not assert that automatic continuation is enabled.
Token figures are the last persisted accounting snapshot, not live token streaming or percentage
of objective completion. Narrow terminals omit token figures first. The line hides when there is
no Target or the TUI is on its home screen.
The plugin installation directory and Target state directory are different locations. On Windows,
the user plugin location is normally %USERPROFILE%\\.config\\opencode\\plugins\\target.js; Target
state is stored separately under %LOCALAPPDATA%\\opencode-target\\. See
docs/operations.md and docs/state-format.md.
The package is private and is not published to npm. Install it from the repository or a locally
built tarball (npm pack), then configure:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-target"]
}OpenCode's plugin API also accepts an options tuple. Example:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
[
"opencode-target",
{
"max_target_token_budget": 200000,
"auto_continue": true,
"min_continue_interval_ms": 250,
"verification": [
{ "id": "tests", "command": "npm test", "requirementID": "tests" }
]
}
]
]
}/target <objective>
/t <objective>
Create a new active Target. A new Target may replace only a completed Target; an unfinished Target must be completed or cleared first.
/target --budget 120000 <objective>
Start with an explicit token budget.
/target
/target status
Show current Target state.
/target doctor
Diagnose persisted state and the OpenCode message/child-session APIs. Invalid plugin options fail
at startup instead of being silently coerced. A malformed JSON state file is moved into the
corrupt/ directory under the configured state directory and reported as an error rather than
being treated as an absent Target.
/target edit <new objective>
Edit the objective without silently changing lifecycle status. A completed Target is immutable; start a new Target instead, which gives it a fresh target_id and resets usage.
/target pause
/target pause waiting for external API access
Pause it.
/target resume
Resume a paused/blocked/usage-limited Target. Resume resets runtime streak audits. An exhausted Target remains budget_limited until its budget is increased or removed.
/target budget
/target budget 250000
/target budget off
View/change the token budget. Changing budget does not itself resume the Target. budget off is available only when max_target_token_budget is not configured; when a maximum/default budget is configured, every Target remains capped by that configured limit.
/target clear
Delete persisted Target state.
/target help
Show command help.
Target registers three tools, intentionally mirroring the authority split of Codex Goal:
When create_target receives explicit acceptance requirements, Target persists them and exposes
add_target_evidence. Every declared requirement must have evidence before the model can mark the
Target complete. Targets without declared requirements retain the existing completion policy.
Returns the persisted Target, status, token budget, root/descendant token usage, active time and remaining tokens.
Creates a Target only when Target mode was explicitly requested. Ordinary tasks must not be silently converted into Targets.
The model can request only:
complete
blocked
paused
It cannot use this tool to set:
active
usage_limited
budget_limited
Those transitions belong to the user/runtime.
paused is allowed only when the user explicitly asks to pause. blocked is reserved for a repeated genuine impasse. complete requires a verification audit.
user/runtime
+-------------------------+
| |
v |
active --------------------> paused
| \ |
| \ | resume
| +-------------------> blocked
| |
| +-------------------> usage_limited
| |
| +-------> active
|
+---- budget exhausted --> budget_limited
|
+---- verified done -----> complete
Persisted statuses:
activepausedblockedusage_limitedbudget_limitedcomplete
Only active is eligible for automatic continuation.
budget_limited has precedence over paused and blocked. A simple resume cannot bypass an exhausted budget.
Every automatic continuation receives fresh internal Target context. The policy tells the model to:
- preserve the full requested end state rather than shrink scope to an easier green subset;
- inspect authoritative current state (worktree, tests, processes, artifacts, external state) rather than rely on stale narrative context;
- distinguish meaningful progress, verified waiting, and no progress;
- treat completion as unproven until every explicit requirement has matching current evidence;
- avoid equating “I found no obvious problem” with proof that a broad requirement is satisfied;
- call
update_target(complete)only when all required work is verified complete; - use
blockedonly after the same genuine blocker persists across at least three consecutive Target turns and no meaningful alternative remains; - never self-pause merely because work is difficult, lengthy, uncertain, or would benefit from clarification.
The original objective is wrapped as untrusted user task data so the internal control policy remains structurally distinct from the objective text.
Target uses session.idle as the scheduling boundary.
At idle it:
- reloads the persisted Target;
- evaluates newly finished root assistant turns;
- accounts root and descendant-session token deltas;
- checks budget/status/circuit breakers;
- consumes any control-command continuation deferral;
- if still
active, atomically records a continuation start; - sends a new synthetic Target context with
session.prompt(); - waits for that physical OpenCode turn to finish;
- accounts and classifies it;
- schedules the next continuation only if the same
target_idremains active.
This is deliberately a sequence of separate physical turns rather than recursive prompting inside one turn.
Every Target has a unique target_id.
Asynchronous accounting/status operations carry the expected ID. If an old callback from Target A arrives after Target B replaced it, the operation is discarded instead of mutating the new state.
This protects against the classic race:
Target A turn still finishing
|
+---------- late usage/status callback
|
user completes A and starts Target B
|
v
Target B must not receive A's mutation
Target follows the important Codex Goal accounting rule as closely as OpenCode's exposed token data allows:
billable target tokens = max(0, input - cached_input) + output
Properties:
- cached input is subtracted;
- plan-mode assistant messages are excluded;
- message accounting is delta-based and idempotent, so streaming/partial updates do not double-count;
- a pre-Target assistant turn first exposed after activation establishes a zero-charge watermark, then only later token growth is charged;
- root session and descendant-session usage are tracked separately;
- descendant sessions are discovered recursively through OpenCode parent/child session relationships;
- reaching a traversal depth/session cap fails the accounting pass instead of accepting partial usage;
- Target activation writes the pre-Target usage baseline atomically with the new state;
- the Target budget uses the combined root + descendant total.
The state records per-message cumulative usage keys only for accounting correctness; the map is bounded to prevent unbounded state-file growth.
If accounting crosses the Target token budget while status is active, the same persisted update sets:
status = budget_limited
Automatic continuation then stops.
Target sends one budget-wrapup turn instructing the model not to begin new substantive Target work and to report useful progress, remaining work, and a clear next action.
A budget change alone does not reactivate the Target:
/target budget 300000
/target resume
If tokens already used are still greater than or equal to the new budget, resume normalizes back to budget_limited.
Target includes two independent runtime circuit breakers, both defaulting to a threshold of 3.
When a Target turn has a failed execution tool call and no successful tool call, the failed-exec streak increases. A successful tool call resets it. At the threshold, the runtime marks the Target blocked.
Only automatic Target continuation turns count. If a continuation produces neither meaningful activity nor final text, the empty-auto streak increases. Ordinary user turns do not count. At the threshold, the Target is runtime-blocked.
Turn classification is idempotent by assistant message ID, preventing duplicate event delivery from incrementing streaks twice.
For an active Target:
- abort/cancel/interruption ->
paused; - rate/quota/usage-limit error ->
usage_limited; - other session runtime error ->
blocked; - an explicit rate/quota/usage-limit error may supersede
budget_limited, matching Codex; pause/blocked transitions do not.
This coupling matters because aborting inference while leaving the Target active would cause the idle scheduler to immediately restart it.
The JSON Target state, not conversation history, is the source of truth. During OpenCode compaction the plugin injects a durable Target summary containing ID, objective, status, budget/usage, elapsed time and continuation count.
While an active Target owns continuation, OpenCode's generic compaction auto-continue is disabled to avoid two competing continuation mechanisms.
Automatic local installation stores goals in <project>/.opencode/target/state.
Automatic global installation records its state path in <config-dir>/target/package.json;
updates keep that path. Both installed plugins read the same metadata.
The following defaults apply to global installations and manually copied plugins without metadata.
Default state location:
Linux/macOS:
~/.local/state/opencode-target/
Windows:
%LOCALAPPDATA%\\opencode-target\\
Override:
OPENCODE_TARGET_STATE_DIR=/some/path
Each session has one JSON state file. Writes use a per-session file lock plus temporary-file rename. Stale locks are recoverable after a timeout. Automatic continuation also uses a persisted owner lease, preventing two OpenCode plugin processes that share the same local state directory from starting the same physical turn.
| Option | Default | Meaning |
|---|---|---|
auto_continue |
true |
Automatically create another physical turn when an active Target becomes idle |
host_api_timeout_ms |
30000 |
Maximum time for a host API call; prompt timeouts signal transport cancellation. An unsettled previous prompt pauses Target instead of allowing overlapping continuations |
max_target_token_budget |
none | Maximum and default budget for newly created Targets, matching Codex's max_goal_token_budget behavior |
min_continue_interval_ms |
250 |
Minimum interval between automatic continuations |
failed_exec_threshold |
3 |
Consecutive qualifying failed-exec Target turns before runtime block |
empty_auto_threshold |
3 |
Consecutive empty automatic continuations before runtime block |
descendant_max_depth |
6 |
Maximum child-session depth; accounting fails closed if deeper descendants exist |
descendant_max_sessions |
96 |
Session safety cap; accounting fails closed before returning a partial snapshot |
accounting_retry |
{ maxAttempts: 3, baseDelayMs: 500, maxDelayMs: 5000 } |
Bounded retry policy for transient OpenCode messages/children/prompt API failures |
scheduler_lease_ms |
30000 |
Persisted continuation ownership lease; minimum 1000 ms |
max_automatic_turns |
none | Runtime limit for automatic physical turns per Target |
max_active_seconds |
none | Runtime limit for active wall-clock seconds |
continuation_watchdog_ms |
60000 |
Check for a missing session.idle after async dispatch; pending permission/question requests defer the check |
max_no_progress_turns |
none | Runtime limit for consecutive automatic turns with no activity or final text |
state_dir |
platform state dir | Override persisted Target directory |
command_name |
target |
Main slash command |
alias |
t |
Short slash alias; set false to disable |
verification |
[] |
Trusted verification gates; commands are never read from Target objective data |
Configured gates run explicitly with /target verify or /target verify <gate-id>. They execute
in the current OpenCode project directory and record pass/fail evidence for the linked requirement.
Verification is not started automatically by continuation.
Verification evidence includes a best-effort worktree fingerprint made from git HEAD and
git status --porcelain. When the fingerprint changes, previous evidence becomes stale and the
linked requirement must be verified again. Use /target checkpoint [summary] to save bounded
checkpoint metadata without creating commits or rolling files back.
Target also refreshes the fingerprint automatically at session idle and before a model completion
request, so evidence can become stale without /target verify being the next command.
Verification gates may define a bounded retry policy:
{ "id": "tests", "command": "npm test", "requirementID": "tests",
"retry": { "maxAttempts": 3, "baseDelayMs": 250, "maxDelayMs": 5000 } }Only transient timeout and network/provider errors are retried. A normal non-zero command exit is recorded as failed without repeating the command.
/target report includes verification totals, total attempts, missing/stale evidence, failed gates,
and runtime stop reasons.
src/core.js durable state machine, accounting, prompts, command parser
src/store.js atomic state store, locks, quarantine, Windows rename retry
src/repository.js persistence repository mutation seam
src/ledger.js token extraction, delta watermarks, root/descendant accounting
src/adapter.js typed OpenCode and best-effort Git integration seams
src/scheduler.js continuation exclusion, rechecks, heartbeats, whenIdle settling
src/traversal.js complete bounded child-session usage traversal
src/continuation.js continuation phase decisions and recheck behavior
src/verification.js normalized verification result contract
src/verification-gate.js trusted verification gate execution
src/options.js plugin option parsing and validation
src/host.js OpenCode host session adapter
src/errors.js typed error classification
src/error-lifecycle.js session.error lifecycle handling
src/event-routing.js session event to action routing
src/context-hooks.js compaction auto-continue suppression
src/tool-accounting.js tool-boundary token accounting
src/commands.js slash command handling
src/server.js OpenCode hook wiring and dependency-binding wrappers
dist/target.js self-contained local-plugin build
test/*.test.js focused per-module unit tests
scripts/build-local.mjs
scripts/check-dist.mjs
Install the pinned development dependency and run the complete verification gate. This includes unit tests, server/continuation smoke tests, budget-wrap-up retry coverage, and restart recovery:
npm install
npm run check
The full test command includes the core unit suite and server, continuation, budget-wrap-up, and restart recovery smoke suites. Maintainer and operator procedures are documented in:
CHANGELOG.mddocs/contributing.mddocs/operations.mddocs/release.mddocs/security.mddocs/compatibility.mddocs/state-format.md- Repository description and GitHub Topics
The project also includes project-local OpenCode skills under .opencode/skills/ for plugin
development, verification, and documentation maintenance.
- durable objective separate from conversational history;
- six-status lifecycle;
- model/user/runtime authority split;
- automatic new physical turns while active;
- fresh continuation policy every turn;
- strict completion verification contract;
- three-turn semantic blocker rule in model policy;
- billable-token style accounting with cached input removed;
- descendant-agent/session usage in the root budget;
- budget-limited precedence and resume normalization;
- separate failed-exec and empty-auto circuit breakers;
- interrupt -> pause semantics;
- unique logical Target ID and expected-ID stale-write guards;
- compaction recovery from persisted state.
OpenCode does not expose all of Codex's internal Goal runtime primitives, so exact byte-for-byte behavior is impossible from an external plugin.
-
Slash control commands still create an OpenCode turn.
command.execute.beforecan replace command parts, but it does not expose a universal “handled/no LLM reply” switch. Target therefore produces a short synthetic control acknowledgement and uses continuation deferral so/target statusor/target pausecannot accidentally launch work. -
Budget steering occurs at the next safe turn boundary. Target can persist
budget_limitedat accounting/tool boundaries, but an external plugin cannot inject Codex's native in-flight runtime steering into an already executing model response. It sends one wrap-up turn once the session is idle. -
Descendant accounting is reconstructed. Codex owns internal agent accounting directly. Target traverses OpenCode's parent/child session graph. Detached sessions that are not linked as descendants cannot be attributed safely.
-
Runtime state is JSON rather than OpenCode's internal DB schema. The semantics are preserved with atomic-ish file persistence, locking and
target_idguards, but this is not a native OpenCode database table.
These are platform-boundary differences, not prompt shortcuts.
The included test suite covers:
- lifecycle and fresh
target_idreplacement after completion; - unfinished-Target replacement rejection;
- cached-token accounting formula;
- plan-mode exclusion;
- descendant split and idempotent message deltas;
- seeded activation baselines so pre-Target tokens are excluded;
- atomic budget crossing behavior;
- budget precedence,
usage_limitedoverride, proven-complete override, and resume normalization; - stale expected-ID rejection;
- repeated failed-exec circuit breaker;
- empty-auto circuit breaker;
- turn-classification idempotence;
- resume audit reset;
- continuation deferral;
- command parsing;
- remaining-budget snapshots;
- per-module coverage for options, host adapter, error classification, event routing,
session.errorlifecycle, tool-boundary accounting, verification gates, context hooks, commands, repository, and continuation phases; - scheduler idle settling (
whenIdle/isSettled) and deterministic smoke completion.
Run:
npm testor:
npm run checkTarget was designed after reviewing the current OpenAI Codex Goal implementation (Goal tool spec/executor, runtime scheduler, accounting, persisted goal state and continuation steering) and the current OpenCode plugin/SDK interfaces.
Primary references:
- https://github.com/openai/codex/tree/main/codex-rs/ext/goal
- https://github.com/openai/codex/blob/main/codex-rs/ext/goal/src/spec.rs
- https://github.com/openai/codex/blob/main/codex-rs/ext/goal/src/tool.rs
- https://github.com/openai/codex/blob/main/codex-rs/ext/goal/src/runtime.rs
- https://github.com/openai/codex/blob/main/codex-rs/ext/goal/src/accounting.rs
- https://github.com/openai/codex/blob/main/codex-rs/ext/goal/templates/goals/continuation.md
- https://opencode.ai/docs/plugins/
- https://opencode.ai/docs/sdk/
MIT