Skip to content

Latest commit

 

History

520 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@osovv/vv-opencode

An opinionated agentic development layer for OpenCode — spec-first when it matters, review-driven execution, portable model roles, safer tools, and long-run safety. Under the hood it ships as a set of OpenCode plugins, managed agents, skills, and the vvoc CLI.

npm CI release stars bun MIT

OpenCode owns the mechanics: streaming, model invocation, sandboxing, permissions. The development process — when to clarify, when to plan, when to review, how to survive long runs — it leaves up to you.

vv-opencode adds that process layer. It is a hand-picked workflow crystallized from a year and a half of daily agentic development: you run vvoc install, learn three skills (vv-spec, vv-plan, vv-execute), and get a disciplined spec-to-code pipeline with review gates, portable model roles, and long-run safety — without needing to know how any of it works inside.

The spec pipeline is the most visible part, but it is only one layer. Everything underneath runs on every session whether or not you ever write a spec: each model edits files through the tool it knows best, routine permissions are approved without interrupting your run, secrets never reach the model, and multi-agent work is held together by an explicit state machine instead of prompt luck. If you already have your own spec tooling, keep it — the runtime layers below apply anyway.


Why vv-opencode?

OpenCode is a strong, flexible base, but it intentionally leaves the development process up to you. Left to itself, agentic work tends to drift: requirements get skipped, one agent silently implements and "reviews" itself, multi-agent loops churn through "one more final review", long runs stall on permission prompts, and model choices are hardcoded everywhere.

vv-opencode addresses each of these:

  • Formalized trajectories — small changes stay direct, unclear bugs start with investigation, large changes go through spec and plan, risky implementation uses review loops.
  • Spec-first by default — broad requests become explicit specs, plans, and review gates before implementation, and every artifact is saved as grep-able XML.
  • Review-driven execution — implementation, spec review, and code review are separate steps with bounded retries, not one agent silently doing everything.
  • A state machine for multi-agent work — explicit work items, required reviewers, round limits, and hard stops instead of free-form subagent loops.
  • Portable model choices — agents reference roles like vv-role:smart and vv-role:fast; you map roles to concrete models per machine or project and switch stacks with one preset command.
  • Per-model editing — each model edits through the tool it knows best: DeepSeek gets its str_replace_editor contract, GLM/Qwen/Kimi use the host built-in edit, GPT keeps the host apply_patch path, and unmatched models get the plugin's hashline_edit. Routing is resolved dynamically per session and exposes exactly one edit tool to each model.
  • Provider-neutral web tools — agents get one canonical web_search and web_fetch contract backed by Exa, Brave, Z.AI, native retrieval, or Spider, instead of provider-specific search and reader schemas leaking into your prompts.
  • Long-run safety — Guardian auto-approves routine low-risk permissions (risky ones stay in OpenCode's manual approval flow), and secrets are redacted before they reach the model.
  • Reproducible setupvvoc install / vvoc sync recreate the same workflow on any machine or project.

You just talk to OpenCode normally

You don't need to learn a command surface first. Ask for what you want — vv-controller picks the lightest appropriate trajectory, and explicit skills take over only when the work needs them:

> Rename this field and update its tests.
→ handled directly

> Why does auth occasionally return 401 after a token refresh?
→ root-cause investigation first → targeted fix → verification

> Add organizations with role-based access.
→ vv-spec → approval → vv-plan → approval → vv-execute
                               ├─ implement
                               ├─ spec review
                               └─ code review

Every managed skill can also be invoked explicitly when you want to drive the process yourself.


Quick start

bun add -g opencode-ai
bun add -g @osovv/vv-opencode
vvoc install

vvoc install does four things:

  • pins @osovv/vv-opencode as an OpenCode runtime plugin and registers the same pinned package as the OpenCode TUI plugin;
  • scaffolds the managed agents and skills;
  • writes the canonical vvoc.json config;
  • sets vv-controller as your default OpenCode agent, with the spec, planning, review, reflection, and handoff skills auto-triggered by request type.

The TUI integration requires OpenCode 1.18.2 or newer; vvoc status and vvoc doctor report the installed host version and fail compatibility checks for older releases.

Want to try it without touching your global setup? Scope everything to one project:

vvoc install --scope project
vvoc launch --scope project

Project scope writes only to ./.opencode/ and ./.vvoc/. A plain opencode launch may still apply OpenCode's native config discovery and merge behavior; vvoc launch --scope project is the hard sandbox path — it starts OpenCode with OPENCODE_CONFIG, OPENCODE_TUI_CONFIG, and VVOC_CONFIG pinned to the selected local files, so you can smoke-test vv-opencode in one repository without mutating your primary global setup.

Already installed? Run vvoc sync anytime to refresh plugins, prompts, skills, and presets.


How it works: spec → plan → execute

vvoc keeps larger agentic work from jumping straight into edits. A request first becomes explicit artifacts; only the approved plan gets executed, with bounded implementation and review loops. This trajectory is opt-in per request: small changes never go through it, and if you already run your own spec workflow, you can keep it — nothing else in vv-opencode depends on these skills.

Request / idea
   ↓
vv-spec
   asks clarifying questions
   writes .vvoc/specs/YYYY-MM-DD-<slug>/spec.xml
   waits for spec approval
   ↓
vv-plan
   reads the approved spec
   writes .vvoc/specs/YYYY-MM-DD-<slug>/plan.xml
   defines tasks, contracts, dependencies, and acceptance criteria
   waits for plan approval
   ↓
vv-execute
   applies the approved plan task by task
   runs implementation + review internally
   verifies before moving on
   ↓
Verified result

Inside vv-execute, each plan task goes through a tracked loop:

Each plan task
   ↓
vv-implementer
   implements the focused task and runs targeted verification
   ↓
vv-spec-reviewer
   checks whether the result matches the approved spec
   ↓
vv-code-reviewer
   checks bugs, regressions, maintainability, and missing tests
   ↓
verification
   pass → next task
   fail → bounded retry loop
   needs context / blocked → stop and ask the user

All artifacts for one feature live together:

.vvoc/specs/YYYY-MM-DD-<slug>/
  spec.xml            # what should be built and why
  design-context.xml  # optional design memory
  plan.xml            # how to implement and verify it

Package ids are date-prefixed (YYYY-MM-DD-<slug>, for example 2026-06-24-cache-store) so active packages sort by creation date; the prefix is date-only, never a full timestamp. Spec and plan lifecycle runs through a top-level status: draft while being written, approved after explicit user approval, applied after successful execution. vv-execute archives applied packages by moving the whole directory to .vvoc/specs/archive/YYYY-MM-DD-<slug>-<timestamp>/.

Specs and plans are XML, so requirements, tasks, acceptance criteria, and dependencies stay grep-able. Identity lives in unique element names — tasks and waves as <TASK-T-001>…</TASK-T-001> and <WAVE-1>…</WAVE-1>, spec and plan components as <COMPONENT-CACHE-STORE>…</COMPONENT-CACHE-STORE> — so grep/sed extraction stays exact without a separate query language. Field names use snake_case; component references are bare slugs; plan architecture components always reuse the exact identities declared in spec.xml, so a plan component without a spec component is a lint error, not a convention breach:

grep '<TASK-T-' .vvoc/specs/*/plan.xml      # task ids
grep '<criterion>' .vvoc/specs/*/plan.xml   # acceptance criteria
grep '<task_id>' .vvoc/specs/*/plan.xml     # dependency graph
grep '<COMPONENT-' .vvoc/specs/*/*.xml      # component map across spec and plan

vv-controller explicitly routes vv-spec, vv-plan, and vv-review; vv-execute, vv-reflect, and vv-handoff are available as managed skills for plan execution, durable repository memory, and end-of-session handoff notes.


What's inside

The twelve plugins

Plugin What it does
WorkflowPlugin A state machine over multi-agent work: explicit work items, required reviewers, bounded implementation/review rounds, and hard stops when more context is needed.
ModelRolesPlugin Semantic model roles (vv-role:smart, vv-role:fast, …) instead of hardcoded model IDs in agents, subagents, and commands — resolved per machine or project at startup.
GuardianPlugin Keeps long or AFK runs moving by auto-approving routine low-risk permission requests; anything risky stays in OpenCode's normal manual approval flow.
HashlineEditPlugin Routes each model to exactly one native edit tool (host edit for GLM/Qwen/Kimi, host apply_patch for GPT, str_replace_editor for DeepSeek, hashline_edit for unmatched models) and hides the other edit tools per session.
SystemContextInjectionPlugin Injects universal guidance — including correctness obligations for behavior changes — plus the work policy selected by the orchestration profile into vv-controller at startup, and registers skill discovery; subagents stay unpolluted.
SecretsRedactionPlugin Redacts tokens, keys, emails, and other sensitive values before messages reach the model, restoring them only where local execution needs the originals.
WebToolsPlugin Two provider-neutral tools — web_search and web_fetch — over Exa, Brave, Z.AI, native retrieval, or Spider, with permission checks and normalized output.
ToolHistoryCompactionPlugin Shrinks the context replayed to the model by compacting old tool outputs non-destructively, without touching on-disk history.
AnalyticsPlugin Local-only token and cache telemetry per model step, a live cache NN% indicator in the TUI, and vvoc analytics cache-hit-rate for retrospective comparison.
PeakHoursPlugin Warns or blocks models whose provider is in peak-priced hours right now, suggests connected off-peak providers, and shows a persistent orange banner in the TUI.
SpecGuardPlugin Deterministic host-side verification of spec-package artifacts: annotates reads with lint verdicts and validates writes; in enforce mode refuses writes that would break the format.
ContextTuiPlugin The /context inspector: an honest, scrollable TUI dialog showing context-window usage by category, tool, and MCP server.

Managed agents

All prompt files are scaffolded by vvoc install / vvoc sync:

Agent When it helps
vv-controller Primary agent that follows the concrete work policy selected for the session by the orchestration profile
enhancer Improves rough requests before execution when a clearer prompt would help
vv-implementer Applies a focused approved change and verifies it before reporting completion
vv-spec-reviewer Checks whether implementation matches the requested spec and acceptance criteria
vv-code-reviewer Looks for bugs, regressions, maintainability risks, and missing tests
investigator Finds the root cause first when behavior is unclear or a failure needs diagnosis
guardian Supports GuardianPlugin by reviewing permission requests and auto-approving only routine low-risk ones

Managed prompts and the universal guidance injected into primary sessions carry explicit correctness obligations. For behavior changes, agents derive the material properties that must be preserved from the request and established contracts, separate write scope from impact and verification scope, investigate directly affected consumers, challenge a material assumption with a diagnostic counterexample, and choose verification at the level the risk arises. Controllers tie completion claims to observed evidence rather than status markers; reviewers distinguish no discovered defect from sufficient support for a material claim and can fail a change for a material verification gap. These obligations are prompt-level guidance only: vvoc install / vvoc sync deliver the current wording, changes take effect after an OpenCode restart like all vvoc config changes, and the bundled contract tests check instruction delivery and wording — they do not evaluate or guarantee how a real model behaves.

Managed skills

Two families: vv-* skills guide the work protocol, while vvoc-* skills operate and observe the vvoc/OpenCode tooling itself.

Skill When to use it What it gives you
vv-spec You have a feature or creative request and no agreed contract yet A guided interview, recommended options, and a saved spec in .vvoc/specs/YYYY-MM-DD-<slug>/spec.xml
vv-plan A spec is approved and ready to implement A task-level implementation plan with file targets, contracts, dependencies, and acceptance criteria
vv-execute A plan is approved and you want it applied step by step Ordered execution with verification, an explicit inline-or-classic mode choice, and applied spec/plan archival
vv-review You want findings, not fixes A review-only workflow that reports spec/code issues and stops before implementation
vv-reflect A long development, debugging, ops, or investigation session produced reusable knowledge Durable notes in existing docs or .vvoc/lessons / .vvoc/runbooks for future agents
vv-handoff You are ending a session and want the visible context preserved A redacted XML note at .vvoc/handoff/YYYY-MM-DD-<session-slug>/handoff.xml, written from already-visible context only
vvoc-usage-analytics You ask about token usage, cache hit rate, costs, or caching regressions Read-only analysis across vvoc analytics, the analytics JSONL, and historical opencode.db data

Skills are loaded by OpenCode at session start through config.skills.paths (registered by SystemContextInjectionPlugin); the vv-controller agent's skill-trigger rules invoke them automatically when a request matches their conditions.


CLI at a glance

Command Purpose
vvoc init Interactive bootstrap flow
vvoc install Non-interactive setup and scaffolding
vvoc sync Refresh runtime/TUI plugin entries, agents, prompts, skills, config
vvoc launch Launch OpenCode with deterministic runtime, TUI, and vvoc config sources
vvoc status Show current installation state, including OpenCode version compatibility and TUI registration
vvoc doctor Diagnose OpenCode version/runtime/TUI/vvoc setup problems (exits non-zero on issues)
vvoc config validate Validate canonical vvoc.json
vvoc role list|set|unset Manage model role assignments
vvoc preset list|show|<name> Inspect or apply named presets
vvoc guardian config Print or write the guardian section
vvoc plugin list List OpenCode plugin entries
vvoc plugin enable|disable Toggle a vvoc-managed plugin on or off
vvoc orchestration show|set Show or set the vv-controller orchestration profile
vvoc patch-provider stepfun-ai|codex|deepseek|kimi|alibaba|zai|all Patch OpenCode providers; codex adds subscription-safe OpenAI aliases (also accepts openai), deepseek/kimi/alibaba/zai add vv- reasoning-effort aliases, all patches every provider at once
vvoc completion Install shell completions
vvoc upgrade Upgrade the global package and run follow-up sync; sync failure is reported as a partial upgrade
vvoc analytics cache-hit-rate Aggregate persisted cache hit rate by day, week, month, session, model, provider, project, vvoc version, or OpenCode version
vvoc version Print installed version

Guardian duration overrides use positive whole milliseconds. Both --timeout-ms and --review-toast-duration-ms reject zero, negative, fractional, missing, or malformed values:

vvoc guardian config --print --timeout-ms 30000 --review-toast-duration-ms 5000

Configuration

Mutating commands default to global scope for backward compatibility; add --scope project to write a project-local layer. Read and diagnostic commands accept --scope global|project|effective, where effective resolves in this order:

  1. explicit env override (VVOC_CONFIG / OPENCODE_CONFIG / OPENCODE_TUI_CONFIG)
  2. nearest project layer
  3. global layer
  4. built-in defaults when the command/runtime permits defaults

Canonical project-local paths:

OpenCode config          → ./.opencode/opencode.json(c)
OpenCode TUI config      → ./.opencode/tui.json(c)
vvoc config              → ./.vvoc/vvoc.json
Managed agent prompts    → ./.vvoc/agents/*.md
Managed skills           → ./.vvoc/skills/*/SKILL.md
Spec package directory   → ./.vvoc/specs/YYYY-MM-DD-<slug>/
Handoff notes            → ./.vvoc/handoff/YYYY-MM-DD-<session-slug>/handoff.xml
Repository memory        → ./.vvoc/lessons/*.xml, ./.vvoc/runbooks/*.xml

Legacy root-level ./opencode.json and ./opencode.jsonc are intentionally not used as vvoc project layers.

Global paths:

OpenCode config          → $XDG_CONFIG_HOME/opencode/opencode.json
OpenCode TUI config      → $XDG_CONFIG_HOME/opencode/tui.json(c)
vvoc config              → $XDG_CONFIG_HOME/vvoc/vvoc.json
Managed agent prompts    → $XDG_CONFIG_HOME/vvoc/agents/*.md
Managed skills           → $XDG_CONFIG_HOME/vvoc/skills/*/SKILL.md
Persisted data           → $XDG_DATA_HOME/vvoc/
Usage analytics          → $XDG_DATA_HOME/vvoc/analytics/usage-YYYY-MM.jsonl

Two config surfaces, one pinned package

OpenCode keeps server/runtime plugins and native TUI plugins in separate configuration surfaces. opencode.json(c) is loaded by the core/server plugin runtime and activates vvoc features such as model roles, Guardian, workflow, hashline edit, redaction, and web tools. tui.json(c) is loaded by the terminal UI process and activates the package's ./tui module (the /context inspector). The same pinned package version appears in both files, but OpenCode selects a different public export for each process; headless/server launches therefore never load the UI module.

vvoc install, vvoc init, and vvoc sync conservatively add the pinned base package specifier (for example @osovv/vv-opencode@X.Y.Z) to tui.json(c); sync also migrates the broken legacy @osovv/vv-opencode/tui form and older managed pins. Existing comments, unrelated settings, unrelated plugin entries, and [specifier, options] tuples are preserved; malformed plugin entries fail without rewrite.

Runtime plugins load the effective vvoc.json once during OpenCode startup and share one immutable config snapshot for the lifetime of the process. There is no live reload: restart OpenCode after changing vvoc.json or tui.json(c).

Strict schema, loud failures

The config contract is versioned and published with the package — source of truth at schemas/vvoc/v3.json. vvoc.json must be canonical version 3 and include required sections such as plugins. Existing v1/v2/pre-role, incomplete, malformed, or otherwise invalid config files fail instead of being migrated or repaired. vvoc install and vvoc sync may create a fresh canonical config when no config exists, but they refuse to rewrite an invalid existing vvoc.json; fix the file manually and rerun vvoc sync.

The optional schema-v3 web section follows the same layer precedence and is omitted from generated defaults — see Web tools for provider selection:

"web": {
  "search": { "provider": "exa", "apiKey": "optional-exa-key" },
  "fetch": { "provider": "native" }
}

Diagnostics never mutate

vvoc status and vvoc doctor report the installed OpenCode version, the 1.18.2 TUI minimum, selected runtime/TUI/vvoc config paths, and validation problems without normalizing or rewriting files. vvoc upgrade can still finish the package installation when the follow-up vvoc sync fails; it then reports a partial upgrade, leaves config unchanged, and tells you to fix the invalid config before rerunning vvoc sync.

Runtime compatibility is current-only: Guardian permission replies use the current OpenCode permission reply path (with the current HTTP reply fallback), hashline edit refs must use current hash/context anchors, and sync writes current managed agents without deleting old pre-rename user or command entries.

Release channels

The package publishes through npm dist-tags with exactly one pre-release channel, rc. A default vvoc upgrade resolves only the stable latest dist-tag and never offers a release candidate; pre-release versions are published with --tag rc, so latest can never move onto a candidate.

Opt in explicitly when you want candidates:

vvoc upgrade --rc                # upgrade to the version currently on the rc dist-tag
vvoc upgrade --allow-prerelease  # same behavior, kept as an alias

When the rc dist-tag has no published candidate yet, the command reports it and installs nothing. If you sit on 1.4.0-rc.1 and the stable 1.4.0 ships, the default vvoc upgrade moves you onto the stable release — semver orders 1.4.0 above its candidates. Plugin consumers can install the channel directly with npm i -g @osovv/vv-opencode@rc.

Maintainers release candidates through the same exact-SHA CI-gated flow, with the channel derived from the bumped version:

bun run release:bump -- prerelease --preid rc   # 1.4.0-rc.1, published to the rc dist-tag, GitHub Release marked pre-release
bun run release:bump -- prerelease --preid rc   # next candidate: 1.4.0-rc.2
bun run release:bump -- 1.4.0                   # final stable release, published to latest

An explicit --channel latest|rc argument may only confirm the derived channel; a contradicting value aborts the bump before any commit or publication.

Stability and compatibility

Since 1.0, vv-opencode treats the daily-driver surface as stable. The compatibility surfaces are: vvoc install / vvoc sync / vvoc launch, the managed skill names (vv-spec, vv-plan, vv-execute, vv-review, vv-reflect, vv-handoff), the published package exports, canonical vvoc schema v3, and the date-prefixed .vvoc/specs/YYYY-MM-DD-<slug>/ artifact layout. Breaking workflow or config changes are documented in release notes. The project still prefers conservative, explicit changes over hidden migration magic: user-owned config is never silently clobbered, and invalid current config fails loudly.

Deterministic local launch

Use vvoc launch when the vvoc-selected config files should be the only files OpenCode sees for this run:

vvoc install --scope project
vvoc launch --scope project -- run "hello"

vvoc launch --scope project is strict and non-mutating: if .opencode/opencode.json or .vvoc/vvoc.json is missing, it fails with a hint to run vvoc install --scope project. When the selected .opencode/tui.json(c) exists, launch also sets OPENCODE_TUI_CONFIG; a missing TUI file is not synthesized. --scope effective follows the layered lookup order, and --scope global uses the global config paths.


Deep dives

Model roles & presets

# View current assignments
vvoc role list
vvoc role list --scope effective

# Assign models to roles
vvoc role set default deepseek/vv-deepseek-flash-max
vvoc role set smart zai-coding-plan/vv-glm-5.3-max
vvoc role set fast openai/vv-codex-gpt-5.6-luna-low
vvoc role set reviewer zai-coding-plan/vv-glm-5.3-max --scope project

# Switch provider presets
vvoc preset vv-codex
vvoc preset vv-zai
vvoc preset vv-deepseek
vvoc preset vv-kimi
vvoc preset vv-alibaba
vvoc preset vv-osovv-ds
vvoc preset vv-osovv-zai
vvoc preset vv-osovv-qwen
vvoc preset vv-astra-solo
vvoc preset vv-astra-workers

# Install the explicit-reasoning provider aliases
vvoc patch-provider codex     # vv-codex-gpt-6-astra-max (+ vv-codex-gpt-5.3-codex-spark-medium legacy alias + existing aliases)
vvoc patch-provider deepseek  # vv-deepseek-flash-max (+ vv-deepseek-v4-flash-max, vv-deepseek-flash-high)
vvoc patch-provider zai       # vv-glm-5.3-max + vv-glm-5.3-flash-max (+ vv-glm-5.3-high)
vvoc patch-provider all       # every patch above in one run

Built-in role IDs: default, smart, fast, reviewer, plus any custom lowercase-hyphenated IDs. Presets are partial — applying one only changes the roles it defines. Managed built-in presets (vv-*) are refreshed from the shipped registry as part of the other work vvoc install and vvoc sync already do; neither command activates a preset or writes the active roles or orchestration profile on its own — that only happens when you run vvoc preset <name>.

Every shipped preset declares an explicit role matrix and orchestration profile:

Preset default fast smart reviewer Profile
vv-codex openai/vv-codex-gpt-5.6-terra-high openai/vv-codex-gpt-5.6-luna-low openai/vv-codex-gpt-5.6-sol-xhigh openai/vv-codex-gpt-5.6-sol-xhigh single-session
vv-zai zai-coding-plan/vv-glm-5.3-flash-max zai-coding-plan/vv-glm-5.3-flash-max zai-coding-plan/vv-glm-5.3-max zai-coding-plan/vv-glm-5.3-max balanced
vv-deepseek deepseek/vv-deepseek-flash-max deepseek/vv-deepseek-flash-max deepseek/vv-deepseek-flash-max deepseek/vv-deepseek-flash-max balanced
vv-kimi kimi-for-coding/k3 kimi-for-coding/kimi-for-coding-highspeed kimi-for-coding/vv-kimi-k3-max kimi-for-coding/kimi-for-coding single-session
vv-alibaba alibaba-token-plan/qwen3.8-max alibaba-token-plan/deepseek-v4-flash alibaba-token-plan/vv-qwen3.8-max-xhigh alibaba-token-plan/glm-5.2 single-session
vv-osovv-ds deepseek/vv-deepseek-flash-max openai/vv-codex-gpt-5.6-luna-low deepseek/vv-deepseek-flash-max zai-coding-plan/vv-glm-5.3-max single-session
vv-osovv-zai deepseek/vv-deepseek-flash-max openai/vv-codex-gpt-5.6-luna-low zai-coding-plan/vv-glm-5.3-max zai-coding-plan/vv-glm-5.3-max single-session
vv-osovv-qwen deepseek/vv-deepseek-flash-max openai/vv-codex-gpt-5.6-luna-low alibaba-token-plan/vv-qwen3.8-max-xhigh zai-coding-plan/vv-glm-5.3-max delegated
vv-astra-solo openai/vv-codex-gpt-6-astra-max openai/vv-codex-gpt-5.6-luna-low openai/vv-codex-gpt-6-astra-max zai-coding-plan/vv-glm-5.3-high single-session
vv-astra-workers deepseek/vv-deepseek-flash-high openai/vv-codex-gpt-5.6-luna-low openai/vv-codex-gpt-6-astra-max zai-coding-plan/vv-glm-5.3-high delegated

vv-osovv-ds replaces the former vv-osovv-flash offering and vv-osovv-zai is new; vv-osovv-sol, vv-osovv-flash, and vv-osovv-kimi are no longer shipped. A retired preset definition already present in a saved vvoc.json is preserved in place rather than migrated or removed across vvoc install/vvoc sync; delete it manually if you no longer want it. No automatic provider migration or legacy-name cleanup runs, and applying a preset changes only the roles and profile it declares.

The explicit-reasoning aliases bind API model IDs to fixed efforts in the patched OpenCode provider entries. deepseek-flash is available as vv-deepseek-flash-max (effort max, image input) and the older vv-deepseek-flash-high (high, text-only); vv-deepseek-v4-flash-max remains text-only. On zai-coding-plan, glm-5.3 is available as vv-glm-5.3-high and vv-glm-5.3-max (both text-only) and glm-5.3-flash as vv-glm-5.3-flash-max (text, image, video, and PDF input). The codex aliases keep the established OpenAI reasoning-summary and encrypted-reasoning options. vv-codex-gpt-5.3-codex-spark-medium is retained only for backward compatibility with manual configurations that already reference it: it is legacy/manual compatibility, keeps its disabled inherited effort variants, and is advertised conservatively as text-only, and no shipped preset selects it. The shared fast role also serves explore, Guardian, and small_model — availability for those consumers is not verified until you use it. Real model access, review quality, latency, and Astra-token savings are unmeasured: metadata here describes provider capability and pricing boundaries, not benchmarked behavior, and credentialed smoke runs would require separate authorization.

Orchestration profiles

Four concrete policies control how vv-controller delegates work at runtime:

  • single-session — vv-controller performs exploration, investigation, planning, implementation, and verification directly. Independent reviewer subagents remain available when the user explicitly requests review or when a materially risky completed change benefits from independent cross-model evaluation.
  • balanced — vv-controller keeps architecture, critical reading, and final synthesis in the primary session and may selectively delegate bounded search, investigation, mechanical implementation, or review when that is the lightest safe route. Delegation is optional, not mechanically mandatory.
  • orchestrated — vv-controller uses the full tracked implementer/reviewer workflow with explicit work items, required reviewers, bounded rounds, and hard stops.
  • delegated — implementation is delegated to workers while the primary session keeps architecture, important code reading, task contracts, explicit acceptance, and final synthesis. Source edits, tests, config changes, formatting, lint fixes, and reviewer-requested fixes — including small mechanical ones — go to workers through bounded task packets. Independent review happens at declared plan checkpoints instead of after every task; BLOCKED and NEEDS_CONTEXT remain hard stops, and stopped or exhausted work resumes only through recorded bounded recovery.

Pick a profile explicitly or let a built-in preset select one:

vvoc orchestration show --scope effective
vvoc orchestration set single-session --scope project

Built-in presets declare an orchestration mapping:

Preset Profile
vv-codex single-session
vv-kimi single-session
vv-alibaba single-session
vv-osovv-ds single-session
vv-osovv-zai single-session
vv-astra-solo single-session
vv-astra-workers delegated
vv-osovv-qwen delegated
vv-zai balanced
vv-deepseek balanced

Applying a built-in preset changes both model roles and the root orchestration profile atomically. A custom user-defined preset without an orchestration section preserves the current root profile. vvoc status reports the profile resolved from the selected vvoc source; effective status with no config files reports balanced.

Profiles are enforced through the concrete policy injected into vv-controller at startup — the model only receives its active work instructions and never sees inactive profile alternatives. The policy is behavioral, not a filesystem sandbox: the delegated profile instructs the controller not to write source files (and not to bypass that via shell rewrites), while runtime authorization genuinely enforces acceptance and checkpoint operations. Asynchronous vv-execute classic mode remains available through that skill's explicit inline/classic/delegated selection. Profile changes take effect after an OpenCode restart, like all vvoc config changes.

Workflow work items

Workflow work items are opened with explicit intent. For implementation loops, controllers use:

{
  "items": [
    {
      "key": "implement-feature",
      "title": "Implement feature",
      "mode": "implementation",
      "requiredReviewers": ["spec", "code"]
    }
  ]
}

For review-only reports, use "mode": "review_only". In review-only mode, reviewer FAIL is a completed finding result: required reviewers are collected independently, parallel spec and code reviewers may both return FAIL, and the item does not route to vv-implementer unless the user explicitly requests fixes.

Delegated execution and review checkpoints

Under the delegated profile the workflow plugin also registers two control tools — work_item_decide and work_checkpoint — for the primary vv-controller session only (child sessions, untrusted workspaces, and unverifiable session identity are denied).

Delegated tasks are opened with "mode": "delegated", an explicitly empty requiredReviewers array, and a declared writeScope of workspace-relative files. One worker (vv-implementer) is launched per task with a bounded packet; attempts are bound to the host call identity, so stale or duplicate callbacks fail without side effects. A DONE worker parks the item in awaiting_acceptance — it never closes the item and the worker never accepts its own result. The controller explicitly decides with work_item_decide:

  • accept (with rationale and evidence references) records controller acceptance and reaches ready_to_close. Acceptance is recorded distinctly from an independent reviewer PASS.
  • request_changes returns the task to the implementation path. Controller-directed retries are bounded to an initial attempt plus one correction; re-deciding or reopening never resets that budget. DONE_WITH_CONCERNS requires an explicit concernsDisposition.
  • rework reopens an accepted task only when a failed checkpoint from the same registered run covers it, preserving acceptance history and granting exactly one additional attempt.
  • recover resumes a stopped or exhausted unaccepted task without resetting history. Recovery targets the latest terminal attempt and requires an explicit diagnosis, a changedCondition (the changed condition or approach), verification references, and a stable recoveryId. When ordinary budget remains, recovery simply returns a stopped item to the launch gate; when an extra attempt is necessary, it grants exactly one. The first grant per target is the single autonomous allowance; every further unit requires a fresh root-user message referenced by userMessageId, which the runtime validates for role, owning-session identity, message identity, and a timestamp at or after the stop — never for its wording — and each message authorizes at most one unit per target. A successful recovery authorization only makes the next bounded attempt possible: it is not a successful implementation, not acceptance, and never satisfies a reviewer.

Worker stops stay hard stops for ordinary dispatch: a BLOCKED or NEEDS_CONTEXT result is never re-launched directly, and its recorded outcome and explanation remain historical facts. The same applies to an exhausted budget — an ordinary third launch is refused until a recorded recovery raises it. Accepted items are not recovery targets; their path is checkpoint-authorized rework.

A worker can fail to launch at all: the host task wrapper rejects the call before the worker runs (for example, an unknown provider or model). The host skips the tool.execute.after hook on a thrown execution, so the plugin consumes the failure as a distinct failed attempt from the live call binding instead of leaving inFlightAttempt stranded until a restart. The supported case is deliberately narrow — a fresh ordinary foreground vv-implementer task launch (no background, no task_id resume, no command/subtask path) whose host call errors with the exact Subagent failed (task_id: <child>): wrapper, where the event metadata independently binds the same parent session and child session. When it applies, the attempt is marked failed with the bounded host error as evidence and a completion time, the item stays awaiting_implementer, and the attempt is charged against the same two-attempt budget. The controller can then explicitly retry with a new call; two failed attempts exhaust the normal budget exactly like two completed ones. A failed attempt never counts as DONE, cannot be accepted or decided as a successful completion, and persists across restart. Because a worker may have applied partial edits before failing, inspect the diff before retrying or closing.

This is not universal automatic error recovery. Background or promoted launches, resumed/shared/re-prompted children, cancellation or interruption, any task that entered the tool.execute.after hook (including a protocol/repair failure there), missing or mismatched parent/child metadata, and unknown or non-wrapper host errors all remain fail-closed: the in-flight block is preserved and no retry is authorized from the event alone. The plugin never hydrates workflow state, infers a work item from error text, or resets a whole session because of an error event, and it does not reclassify legacy-mode or checkpoint-reviewer launches.

A worker whose execution finishes but whose final report stays protocol-invalid after the bounded continuation is settled truthfully as a rejected report: the exact call-bound attempt is recorded as a completed execution with status report_rejected, a bounded diagnostic excerpt, and the protocol error code — never as DONE, never as a launch failure, and never as a still-in-flight attempt. When the malformed output contains an explicit VVOC_STATUS: BLOCKED or NEEDS_CONTEXT line, the observed stop is preserved (the item parks in the corresponding hard-stop state) and no continuation runs; otherwise the item returns to the launch gate with the attempt consumed. A rejected report has the same bounded recovery path as any other terminal outcome, and work_item_list distinguishes it from a running attempt, an exhausted budget, and a substantive stop by exposing the remaining budget and the supported next action. Unknown or still-running host outcomes (for example a non-string task output) are never guessed terminal and cannot clear a live attempt.

When a tracked subagent returns a recognized task envelope whose inner result is malformed for the strict protocol — a missing VVOC_STATUS, a missing VVOC_ROUTE, a non-protocol top-block line, or body text without the required blank line — the plugin makes at most one bounded continuation to that same child session. The continuation request carries no tools override (so the child's persisted session permissions are preserved), and its instructions direct the child to preserve the original work item, assignment, role, and write scope; to finish unfinished work with its existing history and currently permitted tools; or, if the work was already complete, to correct only the final report without repeating work or inventing evidence; and to report a truthful post-continuation VVOC_STATUS/VVOC_ROUTE rather than freezing an outdated one. These are instructions to the model, not a mechanical guarantee of compliance: the plugin still reparses the continued output strictly, applies the bounded error-code and explicit-hard-stop gates, and fails closed with the original protocol error and excerpt. Continuation is attempted only once; a valid result never continues, and a failed, empty, or still-malformed continuation is never retried. An explicit VVOC_STATUS: BLOCKED or NEEDS_CONTEXT line is detected before the call, so malformed hard-stop output fails closed through the original protocol error and excerpt instead of being continued.

Independent review happens at checkpoints declared in the plan. An approved delegated plan carries an <execution><mode>delegated</mode> section with <review_checkpoints> (CHECKPOINT-R-NNN identities, each with kind, wave barrier, covered tasks, reviewed scope files, reviewer set, acceptance criteria, and verification commands) and per-task <write_scope> lists. Registration accepts only this supported approved native plan package (the approved spec and plan pair under .vvoc/specs/); a provided plan or conversation-scoped execution stays on its own source and lifecycle rather than being converted into a native package. work_checkpoint handles these actions:

  • register — with planPath, loads an approved active plan plus its linked approved spec, fully lints them, and binds the run to their content hashes and the canonical workspace root. With a generic runId instead, it appends checkpoints to that execution. Re-registering identical inputs is idempotent; changed approved inputs are explicit plan drift, never a progress reset.
  • start — after every covered task is accepted, captures a fingerprint over the checkpoint scope (native scope files and existence, or the generic accepted-attempt coverage) and opens exactly the declared reviewer set as an ordinary review_only item. Writes overlapping an in-flight checkpoint scope are refused.
  • verify — for native runs, derives passed, failed, stale, or stopped from the recorded reviewer results plus freshly recomputed fingerprints and approval hashes. A checkpoint passes only when every declared reviewer passes for the pinned snapshot; a closed review-only FAIL report is findings, not approval; edits during review make the generation stale. The final checkpoint seals the run only via verify with complete: true after every task is accepted, every earlier checkpoint passed, and the complete declared write scope is covered with fresh verification. Completion is refused while a declared reviewer is missing: a skipped reviewer is never fabricated into a pass.
  • review / bind — record one generic reviewer result (optionally binding a reviewer call identity) and settle the generation when every declared reviewer reported.
  • complete — complete a generic execution when every active task is accepted and every assigned checkpoint passed for the current revision, reporting controller_accepted or independently_reviewed honestly.
  • amend — append validated tasks or checkpoints to a generic execution without replaying accepted work.
  • authorize / record_approval / revoke_authority — record a bounded advance authority from an eligible root-user instruction, record a truthful controller-delegated stage approval, or revoke/narrow future authority credits.
  • recover — bounded recovery for a checkpoint generation that stopped (NEEDS_CONTEXT) or exhausted its ordinary generations. Recovery takes the same diagnosis, changedCondition, verification, recoveryId, and optional userMessageId fields as task recovery, with the same one-unit autonomous-then-authorized policy per checkpoint; a generic exhausted checkpoint recovers under a recorded advance authority referenced by runId and authorityId. Recovering a stopped generation settles it into history as a stopped entry — it does not convert the stop into a failure, so checkpoint rework stays locked until a real failed generation exists — and recovering an exhausted checkpoint grants exactly one further generation with a fresh snapshot at the next start. Recovery never satisfies prerequisites, erases a FAIL, or seals a run: only the required passing final review does.

Passed milestones stay historical: later planned edits are covered by later checkpoints or the final gate, not by an old approval. Hard stops (BLOCKED, NEEDS_CONTEXT) are never reset or bypassed by acceptance or checkpoint handling; only a separately recorded recovery decision resumes the affected work. Checkpoint state persists per session and survives restarts; a persisted PASS is not fresh evidence until verify recomputes current hashes.

Source-independent execution is built on one common contract. Requirements may come from a native package, a provided plan reference, or the current conversation: a native package registers with work_checkpoint register (planPath), while a provided-plan or conversation-scoped run registers once with work_item_open carrying an execution descriptor (executionKey, source, goal, boundary) and its first task batch, and later tasks append with the same runId plus amendmentId and rationale. Generic review obligations run through work_checkpoint actions start, review/bind, complete, and amend. The registry validates task and checkpoint identities, exact references, execution boundaries, and acyclic dependencies before anything is published, and a selected source stays authoritative — a failed native package is never silently reopened as weaker generic execution, and a provided document is never converted or executed as commands. Required reviews are exactly those registered by the source, the user, or an explicit controller decision; a task with no assigned review completes honestly as controller-accepted rather than as independently reviewed, and a final checkpoint, when one was assigned, must still cover the current revision before completion. Explicit autonomous authority is recorded through work_checkpoint action "authorize" from an eligible root-user instruction as a bounded advance reserve shared by the whole authorized execution, consumed one unit at a time only after ordinary allowances are exhausted; duplicate calls, replay, restart, and task replacement cannot refill it, and narrowing or revocation disables future credits without erasing history. The same recorded authority can authorize one bounded continuation that predates a stop, while the separate post-stop userMessageId path keeps its own timing rules.

Delegated and checkpoint state persists under $XDG_DATA_HOME/vvoc/workflow/<sessionId>/workflow-state.json at snapshot version 4, which adds the common execution registry and session-wide root-message claims on top of version 3's recovery histories, replay-protected authorization references, recovery-aware budgets, and rejected-report attempts. Existing native plan runs materialize a compatibility registry entry with their original run ids and content hashes. Older supported snapshots load conservatively: version 3 and 2 files hydrate with empty execution registries (version 2 also with empty recovery histories and original budgets), and version 1 files hydrate as legacy records without plan runs; none ever synthesizes acceptance, approval, or recovery. A snapshot written by a newer plugin version is refused rather than reset. Recovery mutations commit on the live store and persist synchronously — a failed write rolls the mutation back, so changed launch permissions appear only after a durable record; terminal report settlements stage, persist, and then commit. Because runtime plugins share the startup config snapshot, a plugin upgrade takes effect after an OpenCode restart.

Plans without an <execution> section keep their legacy meaning; executing them with different acceptance or review obligations requires an explicit agreed plan amendment, and archived plans are never rewritten. In a deterministic dispatch-count scenario, a twenty-task plan with two intermediate code-review checkpoints and one final spec+code checkpoint needs exactly four initial reviewer launches instead of forty — that is a property of the declared checkpoint policy, not a wall-clock or model-quality benchmark.

Edit format routing

HashlineEditPlugin resolves an edit mode per session model and exposes exactly one native edit tool to that model — either a host-owned tool or one of the plugin profiles:

  • edit — the host built-in edit (filePath/oldString/newString/replaceAll) with its native matching layers, prior-read enforcement, and unified diff output. Served to qwen, kimi, and glm cohorts; the plugin registers no edit tool, so the host runtime stays in charge.
  • apply_patch — the host built-in patch tool, shown by the host gate to gpt/codex models; the plugin never overrides or hides it.
  • str_replace_editor — the plugin's DeepSeek dsh contract (view/create/str_replace/insert) with exact-verbatim matching.
  • hashline_edit — the plugin's hash-anchored tool with LINE#HASH#ANCHOR references and anchored read output (default for unmatched models).

The default routing table sends deepseek to str_replace_editor, kimi, qwen, and glm to edit, and gpt/codex to apply_patch; everything else stays on hashline_edit. Patterns match case-insensitively as substrings of the session modelID only; the first matching rule wins. For every mode the plugin hides the other edit tools from the model — including the host edit for the str_replace_editor/hashline_edit cohorts — so each session sees exactly one editing tool.

vvoc sync and vvoc init write this default table into vvoc.json so it is visible and editable. Materialization is conservative: a routing value you have changed is never overwritten; the table is only filled in where it is missing. Override routing in vvoc.json (schema v3) — the plugins["hashline-edit"] entry accepts a boolean or an object:

"plugins": {
  "hashline-edit": {
    "enabled": true,
    "routing": {
      "default": "hashline",
      "rules": { "qwen": "hashline", "deepseek": "str_replace_editor" }
    }
  }
}

Routing changes require an OpenCode restart, like other runtime plugin settings.

Tool history compaction

ToolHistoryCompactionPlugin shrinks the context replayed to the model on every turn without touching on-disk storage. It rewrites only the in-memory message copy through the experimental.chat.messages.transform hook, and only the output of old completed tool parts — input and part structure (callID/type/order) are never changed, so provider tool_use/tool_result stitching stays intact.

The recent working context is never touched: the newest message and the last protectRecentMessages messages (default 8, measured by message recency time with array-order fallback) are always replayed verbatim, regardless of call count, output size, tool class, or parallel batching. Compaction only applies to messages older than that window.

Compaction is tool-classified, not blanket:

  • Retained (never compacted): results that stay relevant for the whole session — webfetch/web_fetch/web readers, web/search tools, skill, and subagent (task/agent) outputs. Retained tools also never consume the per-call protection budget.
  • Old reads collapse to [Read <file>, lines X-Y] (range recovered from the line-numbered output; missing file or range falls back to head/tail pruning, never a fabricated summary).
  • Other ephemeral outputs (bash, grep, glob, …) past outputMaxChars are pruned to headChars + a fixed marker + tailChars. With savePrunedOutput (default on), the full output is written once to $XDG_DATA_HOME/vvoc/tool-output/tool-<callID>.txt and the marker embeds Full output saved to: <path>, so the model can re-read the full content instead of reconstructing it from fragments.

Outside the recent window, the last protectLastCalls completed calls are also protected; error parts and parts already compacted by OpenCode are skipped. Rewrites are deterministic and idempotent (each part is rewritten at most once, and the saved path is deterministic per callID), and a minSavingsChars guard skips rewrites that would churn the prompt cache for a tiny gain.

Config lives in vvoc.json under plugins["tool-history-compaction"] (boolean or object) and is conservatively materialized by vvoc sync/init:

"plugins": {
  "tool-history-compaction": {
    "enabled": true,
    "protectLastCalls": 3,
    "protectRecentMessages": 8,
    "savePrunedOutput": true,
    "minSavingsChars": 2000,
    "outputMaxChars": 2048,
    "headChars": 1200,
    "tailChars": 400,
    "readSlim": true,
    "retainTools": ["webfetch", "web_fetch", "web-reader", "webreader", "search", "brave", "skill", "task", "agent"]
  }
}

Set outputMaxChars to 0 to disable pruning, protectRecentMessages to 0 to disable the message window (only the newest message stays protected), savePrunedOutput to false to skip disk persistence, or "enabled": false to disable the plugin entirely. Changes require an OpenCode restart.

Cache hit rate analytics

AnalyticsPlugin records one line per completed model step — fresh input, cache read, cache write, output, reasoning, recorded cost — to $XDG_DATA_HOME/vvoc/analytics/usage-YYYY-MM.jsonl, attributed with the vvoc version, the OpenCode version (from session telemetry), project, provider, model, and agent. Telemetry never leaves the machine; disable collection with "plugins": { "analytics": false } and delete old monthly files freely.

In the TUI you get a live cache NN% indicator next to the session prompt (green at 80%+, yellow at 50%+, red below, muted n/a before the first cache-eligible step) and a combined footer line • OpenCode <version> · vvoc vX.Y.Z in the sidebar. The indicator is per-session and computed in memory.

Retrospective analysis lives in the CLI:

vvoc analytics cache-hit-rate --group-by day                       # daily trend
vvoc analytics cache-hit-rate --group-by vvoc --since 30d         # compare vvoc releases
vvoc analytics cache-hit-rate --group-by opencode --since 30d     # compare OpenCode upgrades
vvoc analytics cache-hit-rate --group-by session|model|provider|project|week|month
vvoc analytics cache-hit-rate --project my-repo --order hit-rate --limit 10 --json

The hit rate is token-weighted: cacheRead / (cacheRead + cacheWrite + input) over cache-eligible steps; COVERAGE shows the share of steps whose provider reported cache tokens at all, so providers without prompt caching read as n/a instead of a misleading 0%. --since/--until accept Nd/Nw/Nm or YYYY-MM-DD; --order accepts date, steps, or hit-rate.

Agents can run this analysis conversationally too: the managed vvoc-usage-analytics skill answers usage, cache, and cost questions inside a session — including historical comparisons from opencode.db that predate the analytics plugin.

Peak hours

Several providers (DeepSeek, Z.AI, Qwen) bill higher rates during daily or weekday peak windows. PeakHoursPlugin matches the provider of each outgoing message against local schedules and either warns or blocks — it never switches the model for you and never fetches pricing from the network.

Behavior per mode:

  • soft (default): the message goes through and the TUI shows a persistent orange banner in the bottom slot: ⚠ PEAK deepseek until 10:00 UTC · elevated pricing · off-peak now: z-ai, qwen.
  • hard: the LLM request is rejected with the window end, the wait time, and the connected providers that are currently outside peak (PEAK_HOURS_BLOCK: provider "deepseek" is in peak hours until 10:00 UTC (about 3 h). … Connected providers outside peak hours right now: z-ai, qwen. …). The block fires in chat.params, after the message is stored: your message stays in the session history and the block renders as a regular error entry, not a dropped message.

Nothing already in flight is ever killed:

  • a session created before the current window started is grandfathered to soft for its lifetime (graceActiveSessions, on by default — decisions come from persisted session data, so they survive restarts);
  • subagent sessions, managed subagents, and guardian are always soft — the decision to work was already admitted at the parent level;
  • internal OpenCode agents (compaction, title, summary) are exempt entirely.

Schedules match providers, not models. Subscription plan provider ids from the OpenCode catalog (models.dev) are gated: zai-coding-plan and zhipuai-coding-plan map to the z-ai schedule, alibaba-token-plan and alibaba-token-plan-cn map to the qwen schedule. Bare pay-per-token API providers (zai, zhipuai, alibaba, alibaba-cn, openai, …) publish no peak surcharge and are never gated. Unknown providers are never warned about or blocked, and a malformed schedule logs a warning and disables that provider's schedule instead of blocking anything (fail-open).

Config lives in vvoc.json under plugins["peak-hours"] and is conservatively materialized by vvoc sync/init — your edits are never overwritten. The built-in defaults carry a revision date because providers move these clocks (verified 2026-08-21: DeepSeek ×2 surcharge effective 2026-08-16; Z.AI weekday coding-plan clock; Qwen 22:00–08:00 UTC+8 off-peak plan window):

"plugins": {
  "peak-hours": {
    "enabled": true,
    "mode": "soft",
    "graceActiveSessions": true,
    "schedules": {
      "deepseek": { "windows": [{ "start": "01:00", "end": "04:00", "tz": "UTC" }, { "start": "06:00", "end": "10:00", "tz": "UTC" }] },
      "z-ai": { "windows": [{ "start": "06:00", "end": "10:00", "tz": "UTC", "days": [1, 2, 3, 4, 5] }] },
      "qwen": { "windows": [{ "start": "00:00", "end": "14:00", "tz": "UTC" }] }
    }
  }
}

Windows use HH:MM in an explicit timezone (default UTC), may cross midnight ("start": "22:00", "end": "02:00"), and accept an optional days restriction (0=Sunday … 6=Saturday, default all days). A provider entry may override the global mode with "mode": "hard". Set the top-level "mode": "hard" to enforce blocking everywhere, or "enabled": false to disable the plugin entirely. Changes require an OpenCode restart, like other runtime plugin settings.

Spec lint and spec-guard

The spec-package format is verified by deterministic host-side tooling, not by model discipline: models keep grep/sed as their query layer, and vvoc lint plus SpecGuardPlugin enforce the invariants in code.

vvoc lint                                    # lint .vvoc/specs (active packages only)
vvoc lint .vvoc/specs/2026-08-24-cache/      # one package directory
vvoc lint .vvoc/specs/2026-08-24-cache/plan.xml   # one file (its sibling spec joins for cross-file rules)
vvoc lint --archive                          # include archived packages for migration audits
vvoc lint --strict                           # exit 1 on warnings, not only errors
vvoc lint --format json --no-cache           # one JSON verdict object; bypass the cache

The engine checks well-formedness, the attribute ban, the template contract per artifact type, COMPONENT-UPPER-SLUG / TASK-T-NNN / WAVE-N identity patterns and uniqueness, reference integrity (component slugs, task ids), the plan-components-subset-of-spec-components rule, lifecycle vocabulary, and package layout. Severity is lifecycle-aware: a draft document may have empty sections mid-interview and is never an error; approved/applied documents must be complete. Exit codes are stable for scripts: 0 without errors, 1 with errors, --strict escalates warnings. Results are cached content-addressed under $XDG_CACHE_HOME/vvoc/lint/ — a verdict is recomputed whenever any input byte or the lint rule version changes.

SpecGuardPlugin brings the same verdicts into the session:

  • reads: reading an active spec.xml / plan.xml / design-context.xml appends a bounded [spec-guard] verdict to the tool result, so agents see drift before trusting the file; archived files are never annotated;
  • writes: warn (default) appends the verdict after a write; enforce additionally refuses writes whose result would contain ERROR-severity findings — incremental draft editing is never blocked, because draft incompleteness is never an error.
"plugins": {
  "spec-guard": { "enabled": true, "mode": "warn" }
}

Conservatively materialized by vvoc sync/init; enforce blocks only ERROR states; internal failures degrade to a warning log and never break a tool call. Changes require an OpenCode restart, like other runtime plugin settings.

Web tools

WebToolsPlugin exposes exactly two canonical model-facing tools:

  • web_search requests the web_search permission and returns ranked titles, URLs, snippets, and publication dates. Search uses Exa by default, Brave when configured, or the direct Z.AI/Zhipu Tool API for an explicitly selected region.
  • web_fetch requests the web_fetch permission and retrieves a known HTTP or HTTPS URL as Markdown, text, raw HTML, or a direct JPEG, PNG, GIF, WebP, or PDF attachment. Fetch uses local native retrieval by default, Spider for configured textual extraction, or the direct Z.AI/Zhipu Reader Tool API.

The web-tools vvoc plugin toggle is enabled by default. Provider selection belongs to vvoc.json, not to individual model calls:

"web": {
  "search": { "provider": "zai", "region": "international" },
  "fetch": { "provider": "zai", "region": "china" }
}

Supported search providers are exa (default), brave, and zai. Supported fetch providers are native (default, no credential required), spider, and zai. A zai section must set region to either international or china; the plugin never guesses or falls back to another region.

Direct Z.AI endpoint routing:

Region Search Reader Search engine
international https://api.z.ai/api/paas/v4/web_search https://api.z.ai/api/paas/v4/reader search-prime
china https://open.bigmodel.cn/api/paas/v4/web_search https://open.bigmodel.cn/api/paas/v4/reader search_pro

The zai provider calls these documented REST Tool APIs directly. It does not use MCP, install or manage Z.AI MCP servers, or consume GLM Coding Plan MCP quota. Direct requests require ordinary Z.AI/Zhipu API entitlement and may use paid API balance. For fetch, supported image and PDF URLs still return direct attachments; textual targets are sent to the selected regional Reader endpoint.

Credentials resolve in this order:

  1. EXA_API_KEY, BRAVE_API_KEY, SPIDER_API_KEY, or ZAI_API_KEY for the selected provider
  2. web.search.apiKey or web.fetch.apiKey in the effective vvoc.json

Environment variables win when both sources exist; config changes take effect after restarting OpenCode. Config apiKey values may be literal or use ${VAR} placeholders resolved from the OpenCode process environment at startup, matching secretsRedaction.secret; a placeholder referencing an unset or empty variable logs a startup warning naming the config field and the missing variable names, never values. Placeholder-resolved apiKey values become exact-match SecretsRedactionPlugin rules for provider-bound message flows, and WebToolsPlugin diagnostics report only the credential source (env or config), never the value. If a project-layer .vvoc/vvoc.json containing an apiKey is tracked by Git, startup logs warn with the file name only. Prefer environment variables or the global vvoc layer; do not commit credentials.

While web-tools is enabled, its runtime config hook denies the built-in webfetch and websearch permission ids in memory, leaving only web_fetch and web_search in the normal tool surface. It does not rewrite OpenCode files or remove MCP servers. An explicit user permission entry for webfetch or websearch is respected and may intentionally keep that built-in visible. Disable the plugin and restart to restore stock behavior:

vvoc plugin disable web-tools

Unrelated MCP search or reader tools are not removed automatically; disable those separately if you want only the two canonical tools visible.

/context inspector

Run /context inside an active session. Its bounded host-owned dialog has three tabs: Overview, Tools, and MCP. Use left/right arrows or 1, 2, and 3 to switch tabs and up/down to scroll long detail; the measured header remains visible on every tab. Top-line used/remaining values come from the latest assistant turn's provider-reported input, cache-read, and output token counts when OpenCode exposes them.

Overview category rows are provider-neutral estimates derived from observable TUI/SDK state: system instructions, skill catalog, loaded skills, tool schemas, user and assistant messages, tool calls and results, files, and the latest compaction summary. Percentages are always estimated tokens / current model contextLimit; if OpenCode does not expose a positive current limit, the percentage is shown as an em dash rather than using another denominator. Numeric percentages may exceed 100% when estimates drift, while visual bars clamp only their fill at 100%.

The Tools tab separates each observable current tool's persistent schema estimate from its active history estimate, call count, combined total, source, and percentages. When a schema catalog is unavailable, the row says schema unavailable and labels the history-only subtotal as known total rather than presenting a false zero. History includes only tool parts in the active context: the latest compaction summary and subsequent turns. The skill tool remains visible in detail, but its history belongs to Overview's Loaded skill results category so it is not double-counted.

The MCP tab aggregates observable current schema and retained active history by server and nests the attributed tools. OpenCode 1.18.x does not expose connected MCP tool definitions through its public TUI/SDK tool catalog, so connected servers show current tools unavailable and schema unavailable; their known total includes retained history only, while the unexposed schema overhead remains in Unknown/provider-only. Attribution follows OpenCode's sanitized <server>_<tool> naming contract with unique longest-prefix matching; sanitized collisions or other ambiguous ownership fail closed under Other external/plugin with a bounded warning instead of being guessed.

The plugin does not claim to reconstruct the exact final provider request or provide provider-exact tokenization. Hidden provider transformations, plugin-added data, or otherwise unattributable content appears as Unknown/provider-only; when visible estimates exceed provider usage, the dialog reports estimation drift instead of forcing totals to match. Collection reuses OpenCode's existing tool catalog, active parts, model metadata, and MCP status snapshot without issuing extra MCP requests.

The context vvoc plugin toggle defaults to enabled. Disable it with vvoc plugin disable context, then restart OpenCode.


Local development

bun install             # Install dependencies
bun run check           # Typecheck + lint + format check + GRACE markup check + test
bun run fmt             # Auto-format source files
bun run release:check   # Verify package/schema release consistency

Git hooks are managed via lefthook.

Smoke-test the built CLI against an isolated config home:

tmpdir="$(mktemp -d)"
bun run build
bun dist/cli.js install --config-dir "$tmpdir"
bun dist/cli.js status --config-dir "$tmpdir"

Test the local TUI against a freshly built dist/tui.js without publishing or rewriting your selected configs:

bun run tui:local
bun run tui:local -- -s <session-id>
bun run tui:local -- --scope project

The command defaults to effective config resolution. It builds the package, copies the selected tui.json(c) into a temporary isolated config home, replaces only the managed vv-opencode TUI entry with a local file:// URL, preserves unrelated TUI settings and tuple options, and forwards remaining arguments to OpenCode. The original OpenCode, TUI, and vvoc config files are not modified, and the temporary config is removed after OpenCode exits. Restart the command after source changes because runtime plugins do not live reload.

Full release verification:

bun run release:check
bun run check
bun run pack:check

Publishing

The release flow is automated via a local wrapper and an exact-commit, CI-gated GitHub Actions workflow.

bun run release:bump patch   # or minor, major, prerelease, or explicit semver

This will:

  1. Reject if the worktree is dirty
  2. Bump package.json via npm version --no-git-tag-version
  3. Generate a required AI release summary with opencode --pure run
  4. Prepend a ### Summary section plus conventional commit details to CHANGELOG.md
  5. Update schemas/vvoc/v3.json $id to the new version
  6. Run release:check for consistency
  7. Create a release commit without creating a tag
  8. Push only the current branch to origin
  9. Dispatch publish.yml through gh with the exact package version and release commit SHA
  10. Wait for the dispatched CI run to finish successfully
  11. Retry npm metadata propagation, then verify that npm reports the exact release commit as the published gitHead
  12. Create and push the annotated tag locally, then create the GitHub Release through gh

Local prerequisites:

  • opencode must be available from PATH.
  • gh must be installed and authenticated with permission to dispatch/watch workflows and create releases in the repository. gh run watch does not support fine-grained PAT authentication; use a supported gh login such as OAuth or a classic token.
  • The summary model defaults to deepseek/deepseek-v4-flash; override with VVOC_RELEASE_SUMMARY_MODEL=provider/model and the per-attempt timeout with VVOC_RELEASE_SUMMARY_TIMEOUT_MS=120000.
  • Run release:bump from a checked-out branch with branch and tag push access to origin. A normal branch push never publishes by itself; the wrapper explicitly dispatches the workflow for the exact pushed commit.

The GitHub Actions workflow checks out the requested commit SHA, verifies that its package.json version matches the dispatch input, and runs full validation (typecheck, lint, fmt check, tests, build, pack check, and release:check). Only after every gate passes does it publish to npm with provenance. The local wrapper waits for that CI result, retries registry metadata propagation, verifies npm gitHead, and only then uses the maintainer's authenticated git and gh clients to create the annotated vX.Y.Z tag and GitHub Release. This avoids GitHub App token restrictions on tagging commits that contain workflow changes while preserving verification-before-tagging.

The workflow uses npm provenance/trusted publishing (id-token: write) and read-only repository contents access. It can only publish through an explicit workflow_dispatch request; normal branch and tag pushes do not publish. Tag and GitHub Release creation happen locally only after the workflow succeeds. Configure npm trusted publishing for this GitHub repository/package, or adapt the publish step to use an NPM_TOKEN secret if token-based publishing is required.

bun run release:check verifies independently that package.json name, version, and schemas/vvoc/v3.json $id and config format version are all consistent; run it anytime.


License

MIT — see LICENSE.

About

An opinionated agentic development layer for OpenCode — spec-first when it matters, review-driven execution, portable model roles, safer tools, and long-run safety.

Topics

Resources

Stars

13 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages