opencode-workplan gives OpenCode a durable execution plan that can move
between sessions in the same worktree.
The current snapshot, append-only event ledger, and human-readable Markdown
mirror live outside the repository by default. Server tools mutate the plan;
the TUI sidebar and /current-tasks command read the snapshot directly without
sending a prompt, adding a message, or invoking a model.
This is version 1.2.2. It targets OpenCode >=1.18.15 <2.0.0 and has live
compatibility coverage for OpenCode 1.18.15 on Linux with Bun. See
the exact API contract.
- Stable session-local task IDs (
T-001,T-002, ...). - Seven granular tools: create, read, add, update, complete, list, and resume.
- Stable plan IDs and a recoverable catalog for continuing an active plan in a new root session.
- One current task, dependency/cycle enforcement, blockers, cancellation, reopen/restore reasons, optimistic revisions, and exact verification gates.
- Cross-process per-session locks and write-ahead transactions.
- Replayable JSONL events, atomic snapshots, Markdown mirrors, and corruption recovery that preserves damaged originals.
- Bounded every-turn and compaction context derived from persisted state.
- Task-aware subagents bind to an explicit
[T-NNN], must read that task before other tools, and retain read-only access while Main owns every plan mutation. - Read-only sidebar progress with current work and blocker count; clicking it
opens
/current-tasks, which also has/tasksand/workplanaliases.
Prerequisites are Bun and OpenCode 1.18.15 or a compatible v1 release.
cd /absolute/path/to/opencode-workplan
bun install --frozen-lockfile
bun run checkInstall into the current project without changing global configuration:
cd /absolute/path/to/your-project
opencode plugin /absolute/path/to/opencode-workplanOpenCode 1.18.15 detects both package exports and patches the project-scoped
server and TUI configuration in .opencode. To install globally instead:
opencode plugin --global /absolute/path/to/opencode-workplanFor a distributable local artifact, run bun pm pack, inspect the generated
archive, extract it to a stable absolute path, run bun install --production --ignore-scripts in that directory, and install the directory. This project is
intentionally not published to npm.
Restart OpenCode after installation. Server and TUI are separate, target-exclusive modules:
opencode-workplan/server -> dist/server.js
opencode-workplan/tui -> dist/tui.js
The install command writes the same package spec into project server and TUI configuration. Options are supplied as the second tuple item. Use the same storage settings for both targets.
Project server configuration (.opencode/opencode.json):
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
[
"/absolute/path/to/opencode-workplan",
{
"autoStartFirstTask": true,
"injectSystemContext": true,
"injectCompactionContext": true,
"systemContextMaxChars": 8000,
"compactionContextMaxChars": 24000,
"recentCompletedLimit": 5,
"recentHistoryLimit": 20,
"lockTimeoutMs": 5000,
"staleLockMs": 30000,
"generateMarkdownMirror": true
}
]
],
"permission": {
"todowrite": "deny",
"workplan_*": "allow"
}
}Project TUI configuration (.opencode/tui.json):
{
"$schema": "https://opencode.ai/tui.json",
"plugin": [
[
"/absolute/path/to/opencode-workplan",
{
"recentCompletedLimit": 5
}
]
]
}Global configuration uses the corresponding OpenCode global config directory.
Prefer opencode plugin --global ... to select it correctly.
Storage defaults to:
${XDG_STATE_HOME:-$HOME/.local/state}/opencode/workplan
OPENCODE_WORKPLAN_DIR overrides both the default and storageDir and must be
absolute. This is useful for tests and isolated installations:
export OPENCODE_WORKPLAN_DIR="/absolute/private/path/opencode-workplan-state"All options are validated strictly. Unknown keys, relative storage paths, and out-of-range limits stop plugin initialization instead of silently changing behavior.
The main agent should create its authoritative plan before work begins.
Task-created child and nested subagent sessions resolve their OpenCode
parentID chain to the root. Every delegated short description should begin
with the assigned task ID, for example [T-003] Implement cache invalidation.
The plugin validates that task against the attached plan and injects the stable
plan ID, task ID, assignment revision, and required first call into the child
prompt:
workplan_read({ view: "task", taskID: "T-003" })
The targeted view returns the complete task record, plan objective and constraints, current revision, and dependency readiness even when another task is globally current. Bound children must complete that exact read before using any other tool. The binding and successful read are recovered from persisted session history after plugin or OpenCode restarts, including nested ancestry.
Pending tasks can be bound, so independent assignments may run in parallel. An omitted marker logs and injects a warning but continues unbound; a malformed or nonexistent explicit marker fails closed. A task-splitter invoked before an active plan exists is exempt. Subagents may read the attached plan but cannot create, add, update, complete, list, or resume it. Independent and forked sessions have no parent chain and remain isolated until a root agent explicitly resumes a plan.
To continue earlier work in a new root session:
- Call
workplan_listwith no arguments. It returns only plans whose stored worktree exactly matches the current worktree, sorted byupdatedAtnewest first. Every result includes its objective, stable plan ID, status, resumability, progress, current task, revision, and timestamps. - Compare the objectives and choose the clearly relevant active plan. Ask the user if more than one objective plausibly matches.
- Call
workplan_resumewith the exactplanID, listedexpectedRevision, and a concretereason, then callworkplan_read.
Resume transfers ownership; it does not copy or merge plans. It advances the
existing revision exactly once and appends plan.transferred to the original
ledger. The previous root session is detached and receives PLAN_TRANSFERRED
if it tries to read or mutate the plan. Completed and abandoned plans remain
visible in the list but are never resumable. A root session that already owns
an active plan must finish or transfer it before attaching another one.
Update tasks granularly, record newly discovered scope as new tasks, and complete a task only after all exact verification requirements pass.
Available tools:
| Tool | Purpose |
|---|---|
workplan_create |
Root/main agents create or explicitly replace a plan; subagents are rejected. |
workplan_read |
Read summary, active, targeted task, full, or history context. |
workplan_add |
Root/main only: atomically append tasks and allocate stable IDs. |
workplan_update |
Root/main only: edit or transition one task; never completes it. |
workplan_complete |
Root/main only: complete one task with exact passing verification results. |
workplan_list |
List plans for the current worktree, most recently updated first; takes no arguments. |
workplan_resume |
Transfer one active listed plan to the current root session with revision validation. |
Completion results use this shape:
{
"requirement": "bun test passes",
"command": "bun test",
"result": "all tests passed",
"passed": true
}requirement must exactly equal one task verification requirement. Every
requirement needs exactly one result; unknown, duplicate, missing, or failed
results reject completion.
At any time in an active TUI session, run:
/current-tasks
/tasks
/workplan
The existing OpenCode sidebar shows a 12-column progress bar, exact completed
and total counts, percentage, current task, and blocker count. Clicking anywhere
on the workplan summary dispatches the same workplan.current command used by
the slash aliases below. Directory watchers refresh the summary after atomic
snapshot/catalog renames and follow a transferred plan's current binding; all
watchers are closed when the slot or plugin is disposed.
The command opens a centered, scrollable overlay sized to 80% of terminal width
on wide screens and clamped safely on narrow screens. It shows objective,
progress, current task, blockers, pending work, recent completions, revision,
and update time. Press Escape or Ctrl+C to close it. It reads state.json
through the shared read-only core. It does not use api.client, submit a prompt,
append a message, or mutate the plan.
The storage root contains a derived catalog.json and plan directories. The
catalog maps stable plan IDs and root-session bindings; it is rebuilt from
validated snapshots if missing or corrupt. New plans use an opaque storage ID.
Legacy 0.1.1 plans keep their original session-backed storage ID and receive a
deterministic stable plan ID on discovery:
<storage>/
├── catalog.json
└── sessions/<prefix>-<hash>/
├── state.json
├── events.jsonl
└── tasks.md
state.json is the current snapshot, events.jsonl is the replayable
authoritative mutation history, and tasks.md is a generated mirror. A
mutation acquires one cross-process mkdir lock for its session, recovers under
that lock, validates expectedRevision, stages both mirrors, appends and flushes
one event, then atomically renames and flushes the mirrors. Different sessions
have different locks.
Directories are forced to 0700 and state files to 0600 on supported
platforms. Session IDs cannot traverse paths, and writes reject symbolic-link
targets. Command strings in verification results are stored as plain data and
are never executed by the plugin.
Inspect Markdown mirrors outside OpenCode:
find "${OPENCODE_WORKPLAN_DIR:-$HOME/.local/state/opencode/workplan}" \
-name tasks.md \
-type f \
-printNo files are created in a repository by normal plugin operation unless the user
explicitly points storageDir or OPENCODE_WORKPLAN_DIR there.
On a server read or mutation, the plugin validates both snapshot and ledger. It
rebuilds a missing, corrupt, or behind snapshot from valid replayable events and
preserves a corrupt snapshot as state.json.corrupt-<timestamp>. Only an
unterminated, malformed final JSONL record is treated as an interrupted append;
the original ledger is copied to events.jsonl.corrupt-<timestamp> before the
valid prefix is repaired.
If both files are unusable, or if a complete malformed JSONL record appears,
the plugin returns STATE_CORRUPT and does not overwrite the originals. Copy the
entire session directory before manual repair. The TUI sidebar and command
intentionally do not recover or write; they show a safe unavailable/corruption
state, and the command includes the snapshot path.
Common errors:
TASK_INITIAL_READ_REQUIRED: call the exact targetedworkplan_readshown in the injected binding before any other tool.TASK_BINDING_INVALID/TASK_BINDING_PLAN_MISMATCH: correct the task marker or ask Main for a fresh delegation against the attached plan.SUBAGENT_MUTATION_FORBIDDEN: return evidence to Main; only Main records plan changes and completion.SUBAGENT_CREATE_FORBIDDEN: ask the main agent to create the plan.SUBAGENT_RESUME_FORBIDDEN: ask the main/root agent to attach the selected plan.PLAN_TRANSFERRED: the plan now belongs to another root session; list or create plans from the current root.PLAN_COMPLETED/PLAN_ABANDONED: choose a resumable active result fromworkplan_list.SESSION_PLAN_EXISTS: finish or transfer the current session's active plan before attaching another.WORKTREE_MISMATCH: open the plan's stored worktree before resuming it.REVISION_CONFLICT: callworkplan_read, reconcile, then retry with the new revision.DEPENDENCY_INCOMPLETE: finish/cancel the dependency or remove the edge.DEPENDENCY_CYCLE: remove an edge from the reported cycle.VERIFICATION_REQUIRED: submit one passing result for each exact requirement.LOCK_TIMEOUT: retry; an abandoned lock is reclaimed afterstaleLockMs.UNSUPPORTED_SCHEMA_VERSION: upgrade the plugin before reading newer state.
Server diagnostics use OpenCode structured logging and include safe error codes, revisions, task IDs, and hashed session identifiers—not prompts, tokens, environment dumps, source files, secrets, or normal-output stack traces.
Back up the storage root before upgrading. Version 1.2.2 is state-compatible with 1.2.1 and adds task-bound delegation plus stricter child permissions; it does not change the persisted plan schema. Version 1.2.1 adds the read-only TUI sidebar. Version 1.2.0 discovers existing 0.1.1 plans in place, assigns each a deterministic plan ID, and builds the catalog without rewriting its plan snapshot or ledger. The first successful resume records ownership and its stable ID through a normal one-revision transaction. Version 0 snapshots are migrated in memory to schema version 1 and rewritten only through the normal locked recovery path. Unknown future schema versions are never downgraded. No migration deletes tasks or valid ledger events.
bun test
bun run test:reliability
bun run typecheck
bun run lint
bun run build
bun pm packThe suite covers pure transitions, path safety, migrations, atomic storage, stale locks, real competing processes, ledger recovery, all seven direct tool paths, targeted current/pending reads, binding validation and restart recovery, nested and parallel assignments, main-only mutation revision safety, catalog rebuilding, legacy discovery, concurrent transfer, repeated compaction, fake-host TUI behavior, the 15-task reliability scenario, and 100-task performance.
There is no sidebar task editing, cloud sync, external issue-tracker integration,
cross-worktree transfer, task deletion, multi-user plan, native todo synchronization,
or /current-tasks full. Cancelled tasks remain in history. Portable path and
lock behavior is automated; live installation and TUI smoke coverage is
Linux/Bun against OpenCode 1.18.15.