Loops, plans, sandboxing, and code review for OpenCode AI agents
pnpm add opencode-forgeAdd to your opencode.json to enable Forge’s server-side hooks, tools, and agents:
{
"plugin": ["opencode-forge@latest"]
}For TUI features: Also add to your tui.json to enable the sidebar and execution dialog:
{
"$schema": "https://opencode.ai/tui.json",
"plugin": ["opencode-forge@latest"]
}As of OpenCode 1.17.8, OPENCODE_EXPERIMENTAL_WORKSPACES=true is required for the plugin's loop functionality to work. Set it in the environment that launches opencode:
export OPENCODE_EXPERIMENTAL_WORKSPACES=trueWithout this, Forge cannot create loop worktrees and execute-plan / /execute-plan will fail. See Common Issues and Workspace Integration for details.
Forge ships two user-facing surfaces:
- Server plugin — enabled through OpenCode plugin config in
opencode.json. The package declares theserveroc-plugin surface and exports./serverfor the server entrypoint. - TUI plugin — enabled separately in
tui.json. The package declares thetuioc-plugin surface and exports./tuifor the terminal UI entrypoint.
The server plugin provides the core hooks, tools, agents, plan storage, loop orchestration, review persistence, and sandbox support. The TUI plugin layers on the sidebar and execution dialog.
Forge includes a read-only observability Dashboard — a standalone Bun HTTP server (src/dashboard/) that serves a SolidJS single-page app at GET / and JSON state at GET /api/data. Launch it from the TUI command palette (Open dashboard) or via pnpm dashboard. The dashboard never mutates loop, workspace, or storage state.
The dashboard shows a Loops view by default — groups loops by project with filterable project/loop lists, loop detail (plan, sections, findings, usage, audit results, completion summary), and live polled state (5 s interval). Supports #<projectId>/<loopName> deep linking.
All endpoints are read-only (non-GET requests return 404):
| Endpoint | Description |
|---|---|
GET / |
HTML page (inlined SolidJS app) |
GET /api/data |
JSON snapshot of Forge loop/project state |
Execution flow dialog with mode and model selection:
- Plans — architect produces marked plans that are auto-captured to SQL storage
- Execution —
New session,Execute here, andLooplaunch paths for approved plans - Loops — iterative coding/auditing with isolated git worktree and optional Docker sandbox
- Review Findings — persistent, loop-scoped review findings across loop sessions
- TUI — sidebar and execution dialog
- Sandbox — Optional Docker worktree loop isolation with bind-mounted project files
The plugin bundles three user-facing agents plus a hidden auditor-loop variant used by loop audit sessions. See Agents and slash commands for the full reference.
| Agent | Mode | Description |
|---|---|---|
| code | all | Primary coding agent. |
| architect | primary | Read-only planning agent. Researches the codebase, designs implementation plans, and caches them for user approval before execution. |
| auditor | subagent | Read-only code auditor for convention-aware reviews. Invoked via Task tool to review diffs, commits, branches, or PRs against stored conventions and decisions. |
| auditor-loop | primary, hidden | Internal audit agent used for loop-runner audit sessions. |
The auditor agent is a read-only subagent that cannot edit source files or execute plans. It is invoked by other agents via the Task tool to review code changes against stored project conventions and decisions.
Tool restrictions: The auditor cannot use file-editing tools, planning tools, or loop-management tools. See Auditor restrictions.
The architect agent operates as a read-only planner with message-level reinforcement via the experimental.chat.messages.transform hook. Final plans are rendered once in the assistant response between <!-- forge-plan:start --> and <!-- forge-plan:end --> markers, then auto-captured into SQL before execution approval. After user approval via the question tool, execution is dispatched programmatically — no additional LLM calls are needed. The user can view and edit the cached plan from the sidebar or command palette before or during execution.
See Tools reference for full arguments, section-scoping behavior, restart options, and sandbox shell details.
Forge provides these tool groups:
- Plan tools —
plan-read,section-read - Review tools —
review-write,review-read,review-delete - Loop tools —
execute-plan,loop-cancel,loop-status - Sandbox shell —
shwhen a sandbox manager is available
Loops always run in an isolated git worktree; Docker sandbox is used automatically when available.
| Tool | Description |
|---|---|
execute-plan |
Execute a plan using an iterative development loop in an isolated git worktree, or mode: new-session to launch it in a fresh standalone session. Args: title required; plan, loopName, hostSessionId, mode optional. |
loop-cancel |
Cancel an active loop by worktree name |
loop-status |
List active/recent loops or get detailed status by worktree name, including cumulative token usage when available. Supports restart=true to restart any non-completed loop (running, cancelled, errored, stalled). Completed loops are history-only and cannot be restarted. |
execute-plan reads the current session's captured plan when plan is omitted. maxIterations, execution model, auditor model, and sandbox behavior come from configuration or the TUI execution dialog, not direct execute-plan tool arguments.
| Command | Description | Agent |
|---|---|---|
/review |
Run a code review on current changes | auditor (subtask) |
/review-plan |
Review a completed implementation against its original plan | auditor (subtask) |
/execute-plan |
Start an iterative development loop in a worktree (or a fresh session with mode: new-session) |
code |
/loop-status |
Check status of all active loops | code |
/loop-cancel |
Cancel the active loop | code |
On first run, the plugin automatically copies the bundled config to your config directory:
- If
XDG_CONFIG_HOMEis set:$XDG_CONFIG_HOME/opencode/forge-config.jsonc - Otherwise:
~/.config/opencode/forge-config.jsonc
Note: Configuration is stored at ~/.config/opencode/forge-config.jsonc unless XDG_CONFIG_HOME is set.
The plugin supports JSONC format, allowing comments with // and /* */.
You can edit this file to customize settings. The file is created only if it doesn't already exist.
See Configuration reference for all supported options, including loop post-actions, external read directories, TUI keybinds, dashboard, and sandbox resource defaults.
- Config:
~/.config/opencode/forge-config.jsoncor$XDG_CONFIG_HOME/opencode/forge-config.jsonc - Data dir:
~/.local/share/opencode/forgeor$XDG_DATA_HOME/opencode/forge - Logs:
~/.local/share/opencode/forge/logs/forge.log - Log rotation: 10MB
- Prompts:
~/.config/opencode/forge/promptsor$XDG_CONFIG_HOME/opencode/forge/prompts
Agent and command prompts are bundled as editable markdown under src/prompts/ and installed to ~/.config/opencode/forge/prompts/ on first run. Edit any file there to customize an agent (agents/*.md) or slash command (commands/*.md); your edits take precedence over the bundled defaults and are preserved across upgrades. Bundled prompt fixes are re-applied automatically only to files you have not edited (tracked by content hash in ~/.config/opencode/forge/manifests/); delete a file to restore the bundled version on next start.
The manifest files are managed automatically. Do not hand-edit a manifest hash to match a file you changed — doing so makes the startup sync treat your edit as a pristine bundled file and overwrite it on the next upgrade. Just edit the prompt; leave the manifest alone.
The startup sync is intentionally silent and non-destructive: it installs new prompts/skills, refreshes files you have not touched, preserves your edits, and never deletes anything. For deliberate (re)installation, conflict resolution, and cleanup, run the interactive installer:
bunx opencode-forge # or: npx opencode-forgeIt walks through every bundled prompt and skill. New files are installed silently; when an installed file differs from the bundle you are prompted to overwrite, keep your version, or view a diff. Orphaned files left over from older layouts are offered for removal.
Flags for non-interactive use:
| Flag | Behavior |
|---|---|
-f, --force |
Overwrite all conflicting files and delete all orphans |
-k, --keep |
Keep all local versions; never delete anything |
-y, --yes |
Keep edited files, prune orphans (no prompts) |
-n, --dry-run |
Show what would change without writing anything |
--no-prune |
Only report orphaned files; never delete them |
From a checkout, the same tool is available as pnpm setup (runs bun src/install/cli.ts).
Enable logging.enabled to write logs to disk. To use the default log path, omit logging.file or set it to null (an empty string is not treated as a default). Set logging.debug for more verbose output.
The plugin includes a TUI sidebar widget and an execution dialog for launching plans directly in the OpenCode terminal interface.
The sidebar shows Forge's connection status and version. Captured plans live on the server in the plansRepo SQL store; the TUI no longer keeps a local archive or in-TUI editor.
Open the dialog from the command palette as Execute plan (default keybind <leader>f). The plan is sourced from the most recent architect message in the current session — the marked <!-- forge-plan:start --> ... <!-- forge-plan:end --> block is parsed out of the assistant's reply. If no marked plan exists in the session, the dialog will not open and you'll see a toast asking the architect to produce one first.
The dialog provides full control over execution parameters:
Choose from three execution modes:
- New session — Creates a fresh Code session and sends the plan as the initial prompt
- Execute here — Takes over the current session immediately with the plan
- Loop — Prompts the architect to launch an iterative coding/auditing loop via the
execute-plantool in an isolated git worktree (Docker sandbox used automatically when available)
Two model selectors are available:
Execution Model:
- Opens a full model selection dialog with all available providers
- Shows recently used models for quick access (derived from your OpenCode sessions, recent Forge loops, OpenCode favorites, and the global default)
- Displays model capabilities (reasoning, tools support) in descriptions
- Defaults to the most recent Forge loop's selection, falling back to
config.executionModel
Auditor Model:
- Same model selection interface
- Defaults to the most recent Forge loop's auditor selection, falling back to
config.auditorModel→config.executionModel
Selections live on the OpenCode server, not in a TUI-local cache. Every loop execution stamps the chosen execution + auditor model (and variants) into workspace.create.extra.forgeLoop, and the next time the dialog opens it derives defaults and recents from workspace.list() plus the session list. This means the picker is correct even when the TUI runs on a different host than the OpenCode server.
The dialog tracks only loop-mode executions for recents / last-used defaults; New session and Execute here modes do not create a workspace, so they do not contribute to recents.
When installed from the package, the TUI plugin loads automatically when added to your TUI config. The plugin is auto-detected via the ./tui export in package.json.
Add to your ~/.config/opencode/tui.json or project-level tui.json:
{
"$schema": "https://opencode.ai/tui.json",
"plugin": [
"opencode-forge"
]
}The TUI provides a comprehensive model selection dialog when executing plans. The dialog features:
Models are displayed in priority order:
- Recent — Last 10 models, derived from the OpenCode session list, recent Forge loops, OpenCode favorites, and the global default
- Connected providers — Models from currently connected providers
- Configured providers — Models from providers defined in your OpenCode config
- All models — Remaining models sorted alphabetically by provider and model name
Each model shows:
- Model name and provider
- Capabilities (reasoning, tools support)
- Full identifier (e.g.,
anthropic/claude-sonnet-4-20250514)
- "Use default" option at the top to use config defaults
- Recently used models are derived from server-side data each time the dialog opens, so they reflect the latest state across all hosts the user has used.
TUI options are configured in ~/.config/opencode/forge-config.jsonc under the tui key:
Set sidebar to false to completely disable the widget.
For local development, reference the built TUI file directly:
{
"$schema": "https://opencode.ai/tui.json",
"plugin": [
"/path/to/opencode-forge/dist/tui.js"
]
}Plan with a smart model, execute with a fast model. The architect agent researches the codebase and designs an implementation plan; the code agent implements it.
The architect is read-only and must output exactly one final plan between <!-- forge-plan:start --> and <!-- forge-plan:end --> markers. Forge auto-captures that marked plan into SQL storage for the current session.
The captured plan is the source of truth for execution. The architect's own message in the chat history is the human-readable view; programmatic access is via the plan-read tool.
After the architect presents a summary, the user chooses an execution mode from the execution dialog:
- New session — Creates a new Code session and sends the plan as the initial prompt.
- Execute here — The code agent takes over the current session immediately with the plan.
- Loop — The architect is prompted to launch an iterative coding/auditing loop via the
execute-plantool, which creates an isolated git worktree and provisions a Docker sandbox when available.
| Mode | When to choose it |
|---|---|
New session |
Default for normal implementation |
Execute here |
When preserving current context matters |
Loop |
Safer autonomous iteration |
The dialog also lets you pick the execution model, auditor model, and their optional variants (provider-specific reasoning or thinking-effort levels such as low, high, max) at launch time. Selections are remembered as workspace-level preferences and pre-filled on later launches. Variant defaults can be set via config.executionVariant / config.auditorVariant in the plugin config. In-session changes in the dialog override all other sources and persist for the OpenCode instance lifetime only (not across restarts).
For New session and Execute here, execution is immediate — there are no additional LLM calls between approval and execution. The system intercepts the user's approval answer, reads the cached plan, and dispatches it programmatically to the code agent. The architect never processes the approval response. For Loop mode, the architect is instead instructed to launch the loop via the execute-plan tool.
Model and variant selection follows this priority order:
For execution model:
- In-session dialog override (instance lifetime)
config.executionModel- Last-used (per-project workspace)
- Platform default
For auditor model:
- In-session override
config.auditorModelconfig.executionModel(inherit)- Last-used workspace
- Platform default
For execution variant:
- In-session override
config.executionVariant- Last-used workspace
For auditor variant:
- In-session override
config.auditorVariant- Last-used workspace (independent — does not inherit the execution variant)
- No plan found — Ensure the architect output included the
<!-- forge-plan:start -->/<!-- forge-plan:end -->markers; the capture hook only stores plans wrapped in those markers. - TUI shows no plan — Plans are session-scoped on the server; switch to the session where the architect produced the plan.
- Need logs — Set
logging.enabledtotrue, and optionallylogging.debugfor verbose output.
The loop is an iterative development system with four phases, ending with an optional post-completion action:
- Coding phase — A Code session works on the task
- Auditing phase — The Auditor agent reviews changes against project conventions and stored review findings
- Session rotation — A fresh session is created for the next iteration
- Repeat — Audit findings feed back into the next coding iteration
- Post-completion action — After a clean final audit, if configured, a
post_actionphase runs a skill/prompt inside the worktree before teardown (best-effort, not re-audited)
Each iteration runs in a fresh session to keep context small and prioritize speed. After each phase completes, the current session is destroyed and a new one is created. The original task prompt and any audit findings are re-injected into the new session as a continuation prompt, so no context is lost while keeping the window clean.
Audit findings survive session rotation via the review store. The auditor stores each bug and warning using review-write with file, line, severity, and description. At the start of each audit:
- Existing findings are retrieved via
review-read - Resolved findings are deleted via
review-delete - Unresolved findings are carried forward into the review
Loop sessions rotate between code and auditor work, so Forge persists per-session usage rows in loop_session_usage and merges them for loop-status. Detailed status includes cumulative cost, input/output/reasoning/cache token totals, per-model breakdowns, and live active-session output when available.
Loops always run in an isolated git worktree. Sandbox is optional: when Docker is available and sandbox.mode = 'docker' is configured, a sandbox container is provisioned automatically; otherwise the loop runs in worktree-only mode. Changes are auto-committed and the worktree is removed on completion (branch preserved for later merge).
After each coding iteration, the auditor agent reviews changes against project conventions and stored review findings. Findings are persisted via review-write scoped to the current loop. Outstanding severity: 'bug' findings block completion — the loop terminates only when the auditor has run at least once and zero bug-severity findings remain.
A watchdog monitors loop activity. If no progress is detected within stallTimeoutMs (default: 60s), the current phase is re-triggered. After maxConsecutiveStalls consecutive stalls (default: 5), the loop terminates with reason stall_timeout. Use loop-status with restart to resume from the persisted section/iteration.
Loops use the following priority order for model selection:
- In-session dialog override — Changed in the execution dialog (instance lifetime)
config.executionModel— Global execution model fallback- Last-used workspace — Previously selected model for the project
- Platform default — OpenCode's default model
The auditor model follows a similar chain: in-session override → config.auditorModel → config.executionModel (inherit) → last-used workspace → platform default. Variants follow their own priority (see Model Selection Priority).
When launching from the TUI dialog, your selection is remembered and pre-filled on subsequent launches. The dialog also allows selecting a separate model for the auditor phase.
On model errors during execution, automatic fallback to the default model kicks in.
git pushis denied inside active loop sessions- Tools like
questionandexecute-planare blocked to prevent recursive loops and keep execution autonomous
- Slash commands:
/execute-planto start,/loop-cancelto cancel - Tools:
execute-planto start with parameters,loop-statusfor checking progress (with restart capability),loop-cancelto cancel
The loop terminates when any of these conditions is met:
- Max iterations — The global
maxIterationscap is exceeded (0 = unlimited). - Stall timeout — After
maxConsecutiveStallsconsecutive stalls (default: 5). Useloop-statuswithrestartto resume from the persisted section and iteration. - Final audit completion — When no bug-severity review findings remain after the final audit phase. If
loop.postAction.enabledistrue, the loop enters thepost_actionphase before final termination. - Post-action completion — After a clean final audit and a successful post-completion action phase (if configured).
- Consecutive errors — 3 consecutive errors in either phase.
Loops always run in an isolated git worktree. Sandbox is optional: when Docker is available and sandbox.mode = 'docker' is configured, a sandbox container is provisioned automatically; otherwise the loop runs in worktree-only mode.
Forge worktree loops register as OpenCode workspaces, letting you switch between them (and your main project) from the same TUI session without restarting or re-opening anything.
Workspace integration requires the experimental workspace runtime enabled in OpenCode. See Quick Start for the environment variable setup. No forge config option enables or disables this — the toggle is purely on the OpenCode side and must be present before OpenCode starts.
The
OPENCODE_EXPERIMENTAL_WORKSPACESflag is not currently documented on opencode.ai. The authoritative source ispackages/core/src/flag/flag.tsandpackages/opencode/src/effect/runtime-flags.tsin the OpenCode repo.
- Env var set, OpenCode ≥ 1.17.8 → Forge can create the worktree workspace, bind loop sessions to it, and show the loop as a switchable workspace in the TUI.
- Env var unset or older OpenCode →
experimental.workspace.createis unavailable or no-ops, Forge cannot create the loop worktree, andexecute-plan//execute-planfails before iteration starts.
When a worktree loop starts with OPENCODE_EXPERIMENTAL_WORKSPACES=true, forge:
- Calls
experimental.workspace.createwithtype: "forge",branch: null, andextra: { loopName, projectDirectory, workspaceCreatedAt }to register the workspace through theforgeadapter - The adapter's
createhook creates the git worktree (reusing an orphaned branch when possible) and, when configured, provisions the Docker sandbox container - Creates a new Code session pointed at the worktree directory
- Calls
experimental.workspace.warpto bind the session to that workspace - Persists the workspace ID on the loop record (
loops.workspace_id) so the TUI can route clicks on a loop into the correct workspace
The adapter's remove hook commits in-flight changes (when teardown context allows), stops the sandbox container if any, and removes the worktree directory unless the loop is restartable. Branches are preserved for later restart or merge.
If initial workspace creation fails at startup — env var unset, OpenCode version too old, network error, API mismatch — the loop aborts before creating the first loop session. If a workspace disappears after a loop is already running, Forge attempts to re-provision or detach it and continue where possible.
- Loops are launched via the execution dialog (select Loop mode)
- On hosts with workspace support, active loops appear as switchable workspaces alongside your main project
Most common cause: OPENCODE_EXPERIMENTAL_WORKSPACES=true was not set in the environment that launched OpenCode. See Quick Start for setup.
Symptoms include:
execute-planor/execute-planreturns an internal error before the first coding session starts- Forge logs contain
createBuiltinWorktreeWorkspace: workspace.create threw,workspace.create returned no workspace id, orhandleStartLoop: failed to create builtin worktree workspace - No loop worktree appears in the TUI workspace switcher
The flag must be set before OpenCode starts — setting it inside an already-running session is too late. If OpenCode is launched by a desktop app, service manager, shell alias, terminal profile, or wrapper script, set the variable there and fully restart OpenCode.
Run loop iterations inside an isolated Docker container. Sandbox is optional: when Docker is available and configured, Forge provisions a loop container automatically; otherwise loops run in worktree-only mode.
See Sandbox for setup, Docker-in-Docker behavior, host networking, environment passthrough, custom bind mounts, large-output handling, and resource defaults.
- Docker running on your machine
1. Build the sandbox image:
docker build -t oc-forge-sandbox:latest container/The image includes Node.js 24, pnpm, Bun, Python 3 + uv, ripgrep, git, and jq.
The container/Dockerfile ships with the plugin package. If the image is missing when OpenCode starts, Forge shows a warning toast with a "Build sandbox image" command in the palette. You can also trigger the build from the command palette at any time by searching for Build sandbox image, which opens a confirmation dialog and runs docker build automatically.
Restart OpenCode after changing sandbox configuration.
pnpm build # Compile TypeScript to dist/
pnpm test # Run tests
pnpm typecheck # Type check without emittingThe diagram below shows the overall flow of the Forge loop system — from plan capture through iterative coding/auditing phases with section advancement and session rotation.
MIT



{ "tui": { "sidebar": true, "showVersion": true } }