Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
## Generated and bundled files

- `pnpm build` rewrites `src/version.ts`, `src/dashboard/marked-source.ts`, and `src/dashboard/app-bundle.ts`. Edit `package.json`, `src/dashboard/marked.min.js`, or `src/dashboard/app/` respectively, never the generated files.
- The build does not clean `dist/`; remove stale output when deleting or renaming source modules.
- `pnpm build` removes the output directory before compiling (`scripts/build.ts`), so deleting or renaming source modules cannot leave stale output in `dist/`.
- `scripts/build.ts` runs `tsc` first, then `Bun.build` overwrites `dist/index.js` (server) and `dist/tui.js` (TUI) with self-contained bundles. Both must stay bundled so the plugin loads without resolving `node_modules`; the vendored installer mode (`bunx opencode-forge --vendor`) depends on it. `@opentui/*`, `@opencode-ai/plugin/tui`, `solid-js`, and `bun:sqlite` stay external because opencode's runtime provides them.
- `resolveShippedRoot` in `src/utils/shipped-paths.ts` is the only way to locate shipped files on disk. Never derive paths from `import.meta.url` directly: bundling collapses it, which would silently break the migration SQL loader, prompt loading, and bundled-asset sync.
- Bundled prompts (`src/prompts/`) and skills (`skills/`) sync on every plugin load, preserving user edits. The standalone installer handles conflicts and orphan pruning.
- Keep section-summary markers in `src/prompts/agents/auditor-loop-addendum.md` synchronized with constants in `src/utils/section-summary.ts`.
- The section-summary block template is the single `SECTION_SUMMARY_TEMPLATE` in `src/loop/prompts.ts`, built from the marker constants in `src/utils/section-summary.ts`; do not hand-write the marker strings into prompt markdown files or other prompt builders.

## Loop runtime

Expand All @@ -29,9 +31,10 @@

## Sandbox

- `src/sandbox/sbx.ts` is the only module invoking the `sbx` CLI; route through its `SandboxRuntime` facade. `src/sandbox/process.ts` is the only child-process spawner; all shell execution goes through `runCommand`.
- `getSandboxState` is the only liveness primitive; four states: `running`, `stopped` (reusable, never create/evict), `unknown` (query failed), `missing` (may create or evict). `registerActiveSandbox` is the only place a usable sandbox is recorded.
- `container/Dockerfile` must derive from `docker.io/docker/sandbox-templates:shell-docker`; no `ENTRYPOINT`, `CMD`, or `WORKDIR`.
- `src/sandbox/msb.ts` is the sole TypeScript runtime/lifecycle facade and `msb` CLI argument owner; route runtime operations through its `SandboxRuntime` facade. The one required exception is the generated shell shim (`src/sandbox/shell-shim.ts`), which invokes `msb exec` directly when an agent shell command must run inside a sandbox. `src/sandbox/process.ts` is the only child-process spawner; all TypeScript shell execution goes through `runCommand`.
- `buildSandboxWorkspaces` canonicalizes the host side of every mount and leaves `containerDir` as the original path. msb refuses a host path that traverses a symlink and fails the whole sandbox with `ENOTDIR`, which on macOS breaks every `os.tmpdir()` mount because `/var` is a symlink to `private/var`. Never canonicalize the container side: absolute paths handed to the agent must resolve identically inside the sandbox.
- `getSandboxState` is the only liveness primitive; five states: `running`, `stopped` (reusable, never create/evict), `transient` (real but not directly executable: `Created`/`Starting`/`Draining`/`Paused` map here), `unknown` (query failed), `missing` (may create or evict). `Stopped`/`Crashed` map to the reusable `stopped` state because `msb exec` starts them in place. `registerActiveSandbox` is the only place a usable sandbox is recorded.
- `container/Dockerfile` must derive from a plain OCI base and keep the final `USER agent`; `ENTRYPOINT`/`CMD` are ignored because msb runs `agentd` as PID 1.

## Dashboard, storage, and paths

Expand Down
54 changes: 36 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ Add to your `opencode.json` to enable Forge’s server-side hooks, tools, and ag
}
```

### Plugin-directory install

Instead of editing the `plugin` arrays by hand, the installer can wire the plugin into opencode's config directory:

```bash
bunx opencode-forge --link # re-export shim for the current build
bunx opencode-forge --vendor # self-contained copy (portable)
```

From a source checkout, use `pnpm setup --link` or `pnpm setup --vendor`. Both modes also write the `tui.json` `plugin` entry automatically — opencode does not auto-load the TUI plugin from the plugin directory, so the plugin directory alone cannot enable the sidebar and execution dialog. In a non-interactive shell the flags still require `-y`, `-f`, or `-k`.

| | `--link` | `--vendor` |
| --- | --- | --- |
| Picks up a rebuild | Yes — re-exports the live build | No — re-run after upgrade |
| Portable to another machine | No — absolute path to this checkout | Yes |
| Payload in config dir | Shim only | Full copy (~6 MB) |
| Needs re-run after upgrade | No | Yes |

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`:

```bash
Expand All @@ -52,7 +70,7 @@ Forge ships two plugin entrypoints plus standalone management surfaces:

- **Server plugin** — enabled through OpenCode plugin config in `opencode.json`. The package declares the `server` oc-plugin surface and exports `./server` for the server entrypoint.
- **TUI plugin** — enabled separately in `tui.json`. The package declares the `tui` oc-plugin surface and exports `./tui` for the terminal UI entrypoint.
- **Installer CLI** — a standalone CLI accessible via `bunx opencode-forge` or `pnpm setup` (from a source checkout) for installing/upgrading bundled prompts and skills.
- **Installer CLI** — a standalone CLI accessible via `bunx opencode-forge` or `pnpm setup` (from a source checkout) for installing/upgrading bundled prompts and skills, and for installing the plugin itself into opencode's plugin directory (`--link`/`--vendor`/`--unlink`).
- **Dashboard** — a read-only observability interface launchable from the TUI command palette (`Open dashboard`) or via `pnpm dashboard` (source checkouts only).

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.
Expand Down Expand Up @@ -107,11 +125,11 @@ Execution flow dialog with mode and model selection:

- **Plans** — architect authors validated plans directly into SQL storage with `plan-write`/`plan-edit`
- **Execution** — approved-plan launch paths plus direct `/execute-goal` loops in dedicated worktree sessions; plan loops can also target a configured remote opencode server (see [Configuration](docs/configuration.md#remotes)); grouped execution launches features from a PRD as parallel loops
- **Loops** — iterative coding/auditing with isolated git worktree and optional sbx sandbox
- **Loops** — iterative coding/auditing with isolated git worktree and optional msb sandbox
- **Review Findings** — persistent, loop-scoped review findings across loop sessions
- **Group tools** — `launch-group`, `group-status`, `group-cancel` for parallel feature orchestration
- **TUI** — sidebar and execution dialog
- **Sandbox** — Optional sbx worktree loop isolation with bind-mounted project files
- **Sandbox** — Optional msb worktree loop isolation with bind-mounted project files

## Agents

Expand Down Expand Up @@ -142,9 +160,9 @@ Forge provides these tool groups:
- **Plan tools** — `plan-write`, `plan-edit`, `plan-read`, `section-read`, `plan-adjust`
- **Review tools** — `review-write`, `review-read`, `review-delete`
- **Loop tools** — `execute-plan`, `execute-goal`, `loop-cancel`, `loop-status`
- **Sandbox routing** — native `bash`, `glob`, and `grep` tools route into sbx for sandboxed sessions
- **Sandbox routing** — native `bash`, `glob`, and `grep` tools route into msb for sandboxed sessions

Loops always run in an isolated git worktree; sbx is used when enabled, configured, and available.
Loops always run in an isolated git worktree; msb is used when enabled, configured, and available.

| Tool | Description |
|------|-------------|
Expand Down Expand Up @@ -227,7 +245,7 @@ The plugin includes a TUI sidebar widget and an execution dialog for launching p

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.

When sandboxing is configured, the sidebar displays the current session's sbx state. The `Toggle host sandbox` palette command, and optional `tui.keybinds.toggleHostSandbox` binding, enable or disable sandbox routing for the current session and its Task subagents. The TUI also follows replacement code and auditor sessions when a loop rotates, but does not follow unrelated subagent sessions.
When sandboxing is configured, the sidebar displays the current session's msb state. The `Toggle host sandbox` palette command, and optional `tui.keybinds.toggleHostSandbox` binding, enable or disable sandbox routing for the current session and its Task subagents. The TUI also follows replacement code and auditor sessions when a loop rotates, but does not follow unrelated subagent sessions.

### Additional Commands

Expand All @@ -251,7 +269,7 @@ Choose from three execution modes:

1. **New session** — Creates a fresh Code session and sends the plan as the initial prompt
2. **Execute here** — Takes over the current session immediately with the plan
3. **Loop** — Prompts the architect to launch an iterative coding/auditing loop via the `execute-plan` tool in an isolated git worktree (sbx is used when enabled, configured, and available)
3. **Loop** — Prompts the architect to launch an iterative coding/auditing loop via the `execute-plan` tool in an isolated git worktree (msb is used when enabled, configured, and available)

#### Model Selection

Expand Down Expand Up @@ -353,7 +371,7 @@ After the architect presents a summary, the user chooses an execution mode from

- **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-plan` tool, which creates an isolated git worktree and provisions sbx when enabled, configured, and available.
- **Loop** — The architect is prompted to launch an iterative coding/auditing loop via the `execute-plan` tool, which creates an isolated git worktree and provisions msb when enabled, configured, and available.

| Mode | When to choose it |
|------|-------------------|
Expand Down Expand Up @@ -401,7 +419,7 @@ Loop sessions rotate between code and auditor work, so Forge persists per-sessio

### Worktree Isolation

Loops always run in an isolated git worktree. Sandbox is optional and enabled only when the `sbx` daemon is available and configured (`sandbox.mode = 'sbx'`): when available, a sandbox is provisioned automatically alongside the worktree; otherwise the loop runs in worktree-only mode. Changes are auto-committed and the worktree is removed on completion (branch preserved for later merge).
Loops always run in an isolated git worktree. Sandbox is optional and controlled by `sandbox.enabled` (default `true`) with driver `sandbox.mode = 'msb'`: when enabled, a sandbox is provisioned automatically alongside the worktree. If the `msb` CLI is missing or the host cannot run microVMs, sandbox startup fails and the loop start is rolled back — it never silently falls back to the host. Set `sandbox.enabled: false` to run worktree-only. Changes are auto-committed and the worktree is removed on completion (branch preserved for later merge).

### Auditor Integration

Expand Down Expand Up @@ -490,7 +508,7 @@ All worktree-based execution paths require a git repository with at least one ro
When a worktree loop starts with `OPENCODE_EXPERIMENTAL_WORKSPACES=true`, forge:

1. Calls `experimental.workspace.create` with `type: "forge"`, `branch: null`, and `extra: { loopName, projectDirectory, workspaceCreatedAt }` to register the workspace through the `forge` adapter
2. The adapter's `create` hook creates the git worktree (reusing an orphaned branch when possible) and, when configured, provisions the sbx sandbox
2. The adapter's `create` hook creates the git worktree (reusing an orphaned branch when possible) and, when configured, provisions the msb sandbox
3. Creates a new Code session pointed at the worktree directory
4. Calls `experimental.workspace.warp` to bind the session to that workspace
5. Persists the workspace ID on the loop record (`loops.workspace_id`) so the TUI can route clicks on a loop into the correct workspace
Expand Down Expand Up @@ -526,29 +544,29 @@ Worktree loops require a git repository with at least one commit. OpenCode scope

## Sandbox

Run loop iterations inside an isolated `sbx` sandbox when the `sbx` daemon is available and configured. Sandbox is optional: when `sbx` is enabled, Forge provisions a loop sandbox automatically; otherwise loops run in worktree-only mode.
Run loop iterations inside an isolated `msb` sandbox. Sandbox is optional and controlled by `sandbox.enabled` (default `true`) with driver `sandbox.mode = 'msb'`: when enabled, Forge provisions a loop sandbox automatically. If the `msb` CLI is unavailable or the host cannot run microVMs, sandbox startup fails and the loop is rolled back rather than silently falling back to the host; set `sandbox.enabled: false` to run worktree-only.

See [Sandbox](docs/sandbox.md) for setup, native in-sandbox Docker, network access, environment passthrough, custom bind mounts, large-output handling, and resource defaults.
See [Sandbox](docs/sandbox.md) for setup, host requirements, image building and loading, network access, environment passthrough and secrets, custom bind mounts, large-output handling, and resource defaults.

### Prerequisites

- The `sbx` CLI installed and authenticated (`sbx login`), with the `sbx` daemon running (`sbx daemon start`) on a supported platform (macOS 14+ Apple silicon, Windows 11 with Hypervisor Platform, or Ubuntu 24.04+ with KVM).
- Docker, used only to build the sandbox template.
- The `msb` CLI installed — no account or login step. Install with `curl -fsSL https://install.microsandbox.dev | sh` and verify with `msb doctor` on a supported platform (Linux with KVM, macOS on Apple silicon, or Windows 11 with Windows Hypervisor Platform).
- Docker, required on the host only to build the sandbox image (the msb runtime itself does not need it; Docker *inside* the sandbox is a separate in-image stack).
- OpenCode >= 1.15.5 — sandbox shell routing relies on the session-aware `shell.env` plugin hook. Enforced via `engines.opencode`, so older versions refuse to load the plugin rather than silently running sandbox commands on the host. (Loops additionally require OpenCode >= 1.17.8 for workspace integration, see [Requirements](#requirements).)

### Setup

**1. Build and load the sandbox template:**
**1. Build and load the sandbox image:**

```bash
docker build -t oc-forge-sandbox:latest container/
docker save oc-forge-sandbox:latest -o forge-sandbox.tar
sbx template load forge-sandbox.tar
msb load --input forge-sandbox.tar --tag oc-forge-sandbox:latest
```

The default image includes Node.js (NodeSource current channel), pnpm, Bun, Python 3 + uv, ripgrep, git, and jq. Chromium and Browser Control are an opt-in image feature: set `sandbox.imageFeatures.browserControl` to `true`, then run `Build sandbox template` from the command palette to rebuild and load the configured image tag.
The default image includes Node.js (NodeSource current channel), pnpm, Bun, Python 3 + uv, ripgrep, git, jq, and a full Docker Engine (docker-ce, CLI, containerd, Buildx, and Compose from Docker's official apt repo) that runs natively inside the microVM — `docker run`, `docker build`, and `docker compose` all work in-sandbox. The daemon is started on demand by `forge-dockerd-start` (msb boots its own `agentd` as PID 1 and ignores the image's entrypoint, so nothing runs dockerd at boot); `/var/lib/docker` is backed by a dedicated block device because overlayfs cannot run on a virtiofs mount. The built image is roughly 1.65 GB. Chromium and Browser Control are an opt-in image feature: set `sandbox.imageFeatures.browserControl` to `true`, then run `Build sandbox template` from the command palette to rebuild and load the configured image tag.

The `container/Dockerfile` ships with the plugin package. If the template is missing when OpenCode starts, Forge shows a warning toast with a "Build sandbox template" command in the palette. You can also trigger the build from the command palette at any time by searching for `Build sandbox template`, which opens a confirmation dialog and runs the build/save/load sequence automatically.
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 template" command in the palette. You can also trigger the build from the command palette at any time by searching for `Build sandbox template`, which opens a confirmation dialog and runs the build/save/load sequence automatically.

Restart OpenCode after changing sandbox configuration.

Expand Down
Loading