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, plan viewer, execution dialog, and load-plan UI:
{
"$schema": "https://opencode.ai/tui.json",
"plugin": ["opencode-forge@latest"]
}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 sidebar, plan viewer/editor, execution dialog, and load-plan UI.
Plan viewer showing the plan in rendered markdown format:
Execution flow dialog with mode and model selection:
Plan editor with raw text editing:
Load plans dialog showing archived plans:
- 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, branch-aware review findings across loop sessions
- TUI — sidebar, plan viewer/editor, execution dialog, and load-plan UI
- Sandbox — Optional Docker worktree loop isolation with bind-mounted project files
The plugin bundles three agents:
| Agent | Mode | Description |
|---|---|---|
| code | primary | 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. |
The auditor agent is a read-only subagent (temperature: 0.0) that cannot edit 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 the loop tool to prevent interference with active workflows.
The architect agent operates in read-only mode (temperature: 0.0, all edits denied) with message-level enforcement 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.
Session-scoped plan storage backed by SQL for managing implementation plans. Loop-associated plans are pruned with expired completed loops.
| Tool | Description |
|---|---|
plan-read |
Retrieve the plan. Supports pagination with offset/limit and pattern search. |
section-read |
Read a section plan and its status for the active loop session. Supports reading by index or defaulting to the lowest-index incomplete section. |
Review finding storage for persisting audit results across session rotations.
| Tool | Description |
|---|---|
review-write |
Store a review finding with file, line, severity, and description. Auto-injects branch field. |
review-read |
Retrieve review findings. Filter by file path or search by regex pattern. |
review-delete |
Delete a review finding by file and line. |
Iterative development loops with automatic auditing. Loops always run in an isolated git worktree; Docker sandbox is used automatically when available.
| Tool | Description |
|---|---|
loop |
Execute a plan using an iterative development loop in an isolated git worktree. Args: title required; plan, loopName, and hostSessionId optional. |
loop-cancel |
Cancel an active loop by worktree name |
loop-status |
List all active loops or get detailed status by worktree name. Supports restart to resume inactive loops. |
loop reads the current session's captured plan when plan is omitted. maxIterations, execution model, auditor model, decomposition, and sandbox behavior come from configuration or the TUI execution dialog, not direct loop tool arguments.
| Command | Description | Agent |
|---|---|---|
/review |
Run a code review on current changes | auditor (subtask) |
/loop |
Start an iterative development loop in a worktree | 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.
- 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
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.
dataDir- Data directory for plugin storage (SQL stores, logs). When empty, resolves to~/.local/share/opencode/forge(orXDG_DATA_HOMEequivalent) (default:"")completedLoopTtlMs- TTL for completed/cancelled/errored/stalled loops before sweep (default:604800000/ 7 days).executionModel- Model override for plan execution sessions, format:provider/model(e.g.anthropic/claude-sonnet-4-20250514). When set, plan execution (via the architect's approval flow or the TUI Execute panel) uses this model for the new Code session. When empty or omitted, OpenCode's default model is used (typically themodelfield fromopencode.json). Recommended: Set this to a fast, cheap model (e.g. Haiku or MiniMax) and use a smart model (e.g. Opus) for the Architect session — planning needs reasoning, execution needs speed. This value is used as a fallback when no per-launch selection is made.auditorModel- Model override for the auditor agent (provider/model). When set, overrides the auditor agent's default model. When not set, uses platform default (default:""). This value is used as a fallback when no per-launch selection is made.agents- Per-agent temperature overrides keyed by display name (e.g.,"code","architect","auditor"). Temperature range:0.0-2.0(default:undefined)
logging.enabled- Enable file logging (default:false)logging.debug- Enable debug-level log output (default:false)logging.file- Log file path. Omitted ornullfalls back to~/.local/share/opencode/forge/logs/forge.log(default:""). Setting to an empty string""passes the empty string through and logging will fail silently. Logs remain in the data directory, only config has moved.
When enabled, logs are written to the specified file with timestamps. The log file has a 10MB size limit with automatic rotation.
compaction.customPrompt- Use a custom compaction prompt optimized for session continuity (default:true)compaction.maxContextTokens- Maximum tokens for context during compaction (default:0/ unlimited)
messagesTransform.enabled- Enable the messages transform hook for Architect read-only enforcement (default:true)messagesTransform.debug- Enable debug logging for messages transform (default:false)
loop.enabled- Enable iterative development loops (default:true)loop.defaultMaxIterations- Default max iterations for loops, 0 = unlimited (default:15)loop.cleanupWorktree- Auto-remove worktree on cancel (default:false)loop.stallTimeoutMs- Watchdog stall detection timeout in milliseconds (default:60000)loop.maxConsecutiveStalls- Number of consecutive stalls before the loop terminates with reasonstall_timeout. Set to0to disable stall-based termination (default:5).loop.worktreeLogging.enabled- Enable worktree loop completion logging (default:false)loop.worktreeLogging.directory- Directory for completion logs, defaults to platform data dir (default:"")
sandbox.mode- Sandbox mode:"docker"(optional; Docker sandbox is provisioned automatically when available)sandbox.image- Docker image for sandbox containers (default:"oc-forge-sandbox:latest")sandbox.resources- Container resource limits mapped directly todocker runflags:memory- Memory limit, e.g.,'8g'. Maps to--memory.memorySwap- Memory+swap limit, e.g.,'12g'. Maps to--memory-swap.cpus- Number of CPUs, e.g.,'4','2.5'. Maps to--cpus.shmSize- Shared memory size, e.g.,'1g'. Maps to--shm-size.
decomposer.enabled- Enable plan decomposition into sections (default:true)decomposer.mode- Decomposition mode:"agent"(LLM) or"deterministic"(parser). Defaults to"agent".decomposer.model- Model override for the decomposer agent. Ignored in deterministic mode.decomposer.onParseFailure- Fallback when deterministic parse fails:"legacy"(skip decomposition) or"agent"(try agent mode). Defaults to"legacy".decomposer.maxSections- Maximum number of sections per plan (default:12).
tui.sidebar- Show the forge sidebar widget in OpenCode TUI (default:true)tui.showVersion- Show plugin version number in the sidebar title (default:true)tui.autoSavePlans- Auto-save captured plans to disk under<dataDir>/plans/<projectId>/. Default:false.tui.planArchiveTtlMs- TTL in ms for archived plans before pruning. 0 disables pruning. Default:604800000(7 days).tui.keybinds.viewPlan- View plan dialog keybind. Default:<leader>v.tui.keybinds.loadPlan- Load archived plans dialog keybind. Default:<leader>i.
The plugin includes a TUI sidebar widget and dialog system for viewing, editing, executing plans directly in the OpenCode terminal interface.
The sidebar provides quick access to plans and configuration:
- Auto-save plans — Toggle checkbox to auto-save captured plans to disk under
<dataDir>/plans/<projectId>/ - Load plan — Opens the Load Plan dialog with archived plans from disk
- Plan indicator — When a plan exists, shows
· planin collapsed sidebar header
When an architect session produces a plan, it is cached in the current session plan store. The plan is accessible from the sidebar (Load plan button) or the command palette (Forge: View plan). Missing plans show an informational toast.
The plan viewer dialog renders the full plan as GitHub-flavored markdown with syntax highlighting:
- View tab — Rendered markdown view with full formatting
- Edit tab — Raw text editor for direct plan modification. Click Save to write changes back to the current session plan store
- Execute tab — Opens the execution dialog with mode and model selection
- Export — Exports the plan to a markdown file in the project directory
The Execute tab provides a comprehensive dialog for launching plans with 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 — Launches an iterative coding/auditing loop 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 (last 10, 90-day TTL) for quick access
- Displays model capabilities (reasoning, tools support) in descriptions
- Defaults to last-used selection, falling back to
config.executionModel
Auditor Model:
- Same model selection interface
- Defaults to last-used selection, falling back to
config.auditorModel→config.executionModel
Your selections are automatically saved in tui_preferences after launch:
- Last-used mode and models are persisted per-project (30-day TTL)
- Subsequent plan executions pre-fill with your previous choices
- Recent models are tracked across all dialog interactions
The command palette registers two Forge commands:
Forge: View plan(<leader>v) — View cached plan for this sessionForge: Load plan(<leader>i) — Load an archived plan from disk
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 used across all dialogs (90-day TTL)
- 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 tracked automatically
- Last-used selections are persisted per-project (30-day TTL)
TUI options are configured in ~/.config/opencode/forge-config.jsonc under the tui key:
{
"tui": {
"sidebar": true,
"showVersion": true
}
}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 user can view the cached plan at any time from the sidebar (Load plan button) or the command palette (Forge: View plan). The plan viewer renders full GitHub-flavored markdown and supports inline editing — the user can modify the plan directly before approving.
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 — Creates an isolated git worktree and launches an iterative coding/auditing loop. Docker sandbox is provisioned automatically when available; otherwise the loop runs in worktree-only mode.
| 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 and auditor model at launch time. Those selections are remembered per project and pre-filled on later launches.
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.
Model selection follows this priority order:
For execution model:
- Dialog selection (last-used, persisted per-project)
config.executionModel- Platform default
For auditor model:
- Dialog selection (last-used, persisted per-project)
config.auditorModelconfig.executionModel- Platform default
- No plan found — Ensure the architect output included the forge plan markers, or open the Plan Viewer for the current session.
- TUI shows no plan — The plan is session-scoped; use
Forge: View planin the session where the architect produced it. - Need logs — Set
logging.enabledtotrue, and optionallylogging.debugfor verbose output.
The loop is an iterative development system that alternates between coding and auditing phases:
- 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
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
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 loop's branch. 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:
- Dialog selection — Model chosen in the execution dialog (persisted per-project)
executionModel— Global execution model fallback- Platform default — OpenCode's default model
The auditor model follows a similar chain: dialog selection → auditorModel → executionModel → platform default.
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
questionandloopare blocked to prevent recursive loops and keep execution autonomous
- Slash commands:
/loopto start,/loop-cancelto cancel - Tools:
loopto 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.
- 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.
Worktree loops can optionally 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 is host-gated, not config-gated. Forge uses opencode's builtin worktree workspace type, which is always available on hosts that expose the experimental workspace API (experimental_workspace on the plugin input, experimental.workspace on the SDK client).
- Host exposes the API → worktree loops become workspace-backed. The worktree directory appears as a switchable workspace in the TUI, and its sessions are bound to that workspace.
- Host does not expose the API → forge skips registration, logs a note, and worktree loops run exactly as before. Everything else (iteration, auditing, sandbox, status, cancel, restart) is unaffected.
No forge config option enables or disables this — the feature lights up automatically on supported hosts.
When a worktree loop starts on a supported host, forge:
- Creates the git worktree (as usual)
- Creates a new Code session pointed at the worktree directory
- Calls
experimental.workspace.createwithtype: "worktree"andbranch: nullto create a builtin worktree workspace - 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 adaptor's create and remove hooks are intentional no-ops — forge's loop system owns worktree lifecycle, not the workspace system. The adaptor only surfaces existing worktrees to the workspace UI.
If workspace creation or session binding fails at runtime (network error, API mismatch, unsupported host), the loop does not abort. Forge logs the failure, clears the workspace ID, and the loop continues as a regular (non-workspace) worktree loop. You lose workspace-based switching for that loop, but the loop itself runs to completion.
- Loops are launched via the Execute tab in the Plan Viewer dialog (select Loop mode)
- On hosts with workspace support, active loops appear as switchable workspaces alongside your main project
Run loop iterations inside an isolated Docker container. Three tools (bash, glob, grep) execute inside the container via docker exec, while read/write/edit operate on the host filesystem. Your project directory is bind-mounted at /workspace for instant file sharing.
- 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.
2. Configure the sandbox (~/.config/opencode/forge-config.jsonc):
{
"sandbox": {
"mode": "docker",
"image": "oc-forge-sandbox:latest"
}
}3. Restart OpenCode.
Start a sandbox loop via the architect plan approval flow (select "Loop") or directly with the loop tool:
loop
Sandbox is optional. When Docker is available and configured, a sandbox container is provisioned automatically; otherwise the loop runs in worktree-only mode. The loop:
- Creates a git worktree
- Starts a Docker container with the worktree directory bind-mounted at
/workspace - Redirects
bash,glob, andgreptool calls into the container - Cleans up the container on loop completion or cancellation
- Bind mount -- the project directory is mounted directly into the container at
/workspace. No sync daemon, no file copying. Changes are visible instantly on both sides. - Tool redirection --
bash,glob, andgreproute throughdocker execwhen a session belongs to a sandbox loop. Theread/write/edittools operate on the host filesystem directly (compatible with host LSP). - Git blocking -- git commands are explicitly blocked inside the container. All git operations (commit, push, branch management) are handled by the loop system on the host.
- Host LSP -- since files are shared via the bind mount, OpenCode's LSP servers on the host read the same files and provide diagnostics after writes and edits.
- Container lifecycle -- one container per loop, automatically started and stopped. Container name format:
forge-<worktreeName>.
| Option | Default | Description |
|---|---|---|
sandbox.mode |
"docker" |
Sandbox mode (optional; Docker used when available) |
sandbox.image |
"oc-forge-sandbox:latest" |
Docker image to use for sandbox containers |
The container/Dockerfile is included in the project. To add project-specific tools (e.g., Go, Rust, additional language servers), edit the Dockerfile and rebuild:
docker build -t oc-forge-sandbox:latest container/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 decomposition through iterative coding/auditing phases with section advancement and session rotation.
MIT






{ // Data directory for plugin storage (SQL stores, logs) // When empty, resolves to ~/.local/share/opencode/forge (or XDG_DATA_HOME equivalent) "dataDir": "", // Logging configuration "logging": { "enabled": false, // Enable file logging "debug": false, // Enable debug-level output "file": "" // Log file path (omit or set to null for default path) }, // Session compaction settings "compaction": { "customPrompt": true, // Use custom compaction prompt for continuity "maxContextTokens": 0 // Max tokens for context (0 = unlimited) }, // Messages transform hook for read-only enforcement "messagesTransform": { "enabled": true, // Enable transform hook "debug": false // Enable debug logging }, // Model override for plan execution sessions (format: "provider/model") "executionModel": "", // Model override for the auditor agent (format: "provider/model") "auditorModel": "", // Iterative development loop settings "loop": { "enabled": true, // Enable iterative loops "defaultMaxIterations": 15, // Max iterations (0 = unlimited) "cleanupWorktree": false, // Auto-remove worktree on cancel "stallTimeoutMs": 60000, // Stall detection timeout (60s) "maxConsecutiveStalls": 5, // Consecutive stalls before termination (0 = disabled) "worktreeLogging": { // Worktree loop completion logging "enabled": false, // Enable completion logging "directory": "" // Log directory (defaults to platform data dir) } }, // Docker sandbox configuration for isolated loop execution (optional; provisioned automatically when available) "sandbox": { "mode": "docker", // Sandbox mode: "docker" "image": "oc-forge-sandbox:latest", // Docker image for sandbox containers "resources": { // Container resource limits (maps to docker run flags) "memory": "8g", // Memory limit (--memory) "cpus": "4", // CPU limit (--cpus) "shmSize": "1g" // Shared memory size (--shm-size) } }, // Plan decomposition settings (optional, defaults to agent-based decomposition) "decomposer": { "enabled": true, // Enable plan decomposition "mode": "agent", // Decomposition mode: "agent" or "deterministic" "maxSections": 12 // Maximum number of sections }, // TUI sidebar widget configuration "tui": { "sidebar": true, // Show Forge sidebar in OpenCode TUI "showVersion": true, // Show plugin version in sidebar title "autoSavePlans": false, // Auto-save captured plans to disk under <dataDir>/plans/<projectId>/ "planArchiveTtlMs": 604800000, // TTL in ms for archived plans before pruning. 0 disables pruning. "keybinds": { // Keyboard shortcut overrides "viewPlan": "<leader>v", // View plan dialog "loadPlan": "<leader>i" // Load archived plans dialog } }, // TTL in ms for completed/cancelled loops before cleanup. Default: 604800000 (7 days) "completedLoopTtlMs": 604800000, // Per-agent overrides (temperature range: 0.0 - 2.0) // Keys are agent display names (e.g., "code", "architect", "auditor") // "agents": { // "architect": { "temperature": 0.0 }, // "auditor": { "temperature": 0.0 }, // "code": { "temperature": 0.7 } // } }