You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(sandbox): replace sbx runtime with msb driver (#95)
* refactor(sandbox): replace sbx runtime with msb driver
* feat(sandbox): in-VM docker, opt-in egress, and plugin-dir installer
* feat(loop): per-section checkpoint commits and clean-audit summary re-prompt
* chore: bump version to 0.9.0
* feat(sandbox): notify the agent when execution returns to the host
Track which sessions resolve to a container so the system prompt gains a
one-shot note when a session transitions back to the host, and require the
agent to reinstall missing environment tooling instead of excusing it.
Resolution is now fail-closed so an unavailable container is not mistaken
for a host transition. Session tracking is bounded by an LRU cache.
* docs: use pnpm run setup for the source-checkout installer
setup is a built-in pnpm command, so the documented pnpm setup --vendor is
rejected with "Unknown options: 'vendor'" and bare pnpm setup runs pnpm's own
setup instead of the installer. Correct every reference in the README, the
configuration guide, and the paths.ts comment, and note why run is required.
Regenerating the typedoc output also picks up drift that was already committed:
the stale 0.8.9 VERSION, the sandbox max-resource rows, and source links now
pointing at HEAD.
* fix(session): fail closed on undetermined parent lookup
* feat(install): offer to install msb sandbox CLI
* feat(sandbox): stream live progress from the template build dialog
Copy file name to clipboardExpand all lines: AGENTS.md
+8-5Lines changed: 8 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,9 +15,11 @@
15
15
## Generated and bundled files
16
16
17
17
-`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.
18
-
- The build does not clean `dist/`; remove stale output when deleting or renaming source modules.
18
+
-`pnpm build` removes the output directory before compiling (`scripts/build.ts`), so deleting or renaming source modules cannot leave stale output in `dist/`.
19
+
-`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.
20
+
-`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.
19
21
- Bundled prompts (`src/prompts/`) and skills (`skills/`) sync on every plugin load, preserving user edits. The standalone installer handles conflicts and orphan pruning.
20
-
-Keep section-summary markers in `src/prompts/agents/auditor-loop-addendum.md` synchronized with constants in `src/utils/section-summary.ts`.
22
+
-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.
21
23
22
24
## Loop runtime
23
25
@@ -29,9 +31,10 @@
29
31
30
32
## Sandbox
31
33
32
-
-`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`.
33
-
-`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.
34
-
-`container/Dockerfile` must derive from `docker.io/docker/sandbox-templates:shell-docker`; no `ENTRYPOINT`, `CMD`, or `WORKDIR`.
34
+
-`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`.
35
+
-`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.
36
+
-`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.
37
+
-`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.
From a source checkout, use `pnpm run setup --link` or `pnpm run 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`.
51
+
52
+
||`--link`|`--vendor`|
53
+
| --- | --- | --- |
54
+
| Picks up a rebuild | Yes — re-exports the live build | No — re-run after upgrade |
55
+
| Portable to another machine | No — absolute path to this checkout | Yes |
56
+
| Payload in config dir | Shim only | Full copy (~6 MB) |
57
+
| Needs re-run after upgrade | No | Yes |
58
+
41
59
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`:
42
60
43
61
```bash
@@ -52,7 +70,7 @@ Forge ships two plugin entrypoints plus standalone management surfaces:
52
70
53
71
-**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.
54
72
-**TUI plugin** — enabled separately in `tui.json`. The package declares the `tui` oc-plugin surface and exports `./tui` for the terminal UI entrypoint.
55
-
-**Installer CLI** — a standalone CLI accessible via `bunx opencode-forge` or `pnpm setup` (from a source checkout) for installing/upgrading bundled prompts and skills.
73
+
-**Installer CLI** — a standalone CLI accessible via `bunx opencode-forge` or `pnpm run 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`).
56
74
-**Dashboard** — a read-only observability interface launchable from the TUI command palette (`Open dashboard`) or via `pnpm dashboard` (source checkouts only).
57
75
58
76
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.
@@ -107,11 +125,11 @@ Execution flow dialog with mode and model selection:
107
125
108
126
-**Plans** — architect authors validated plans directly into SQL storage with `plan-write`/`plan-edit`
109
127
-**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
110
-
-**Loops** — iterative coding/auditing with isolated git worktree and optional sbx sandbox
128
+
-**Loops** — iterative coding/auditing with isolated git worktree and optional msb sandbox
111
129
-**Review Findings** — persistent, loop-scoped review findings across loop sessions
112
130
-**Group tools** — `launch-group`, `group-status`, `group-cancel` for parallel feature orchestration
-**Sandbox routing** — native `bash`, `glob`, and `grep` tools route into sbx for sandboxed sessions
163
+
-**Sandbox routing** — native `bash`, `glob`, and `grep` tools route into msb for sandboxed sessions
146
164
147
-
Loops always run in an isolated git worktree; sbx is used when enabled, configured, and available.
165
+
Loops always run in an isolated git worktree; msb is used when enabled, configured, and available.
148
166
149
167
| Tool | Description |
150
168
|------|-------------|
@@ -215,7 +233,7 @@ Flags for non-interactive use:
215
233
|`-n`, `--dry-run`| Show what would change without writing anything |
216
234
|`--no-prune`| Only report orphaned files; never delete them |
217
235
218
-
From a checkout, the same tool is available as `pnpm setup` (runs `bun src/install/cli.ts`).
236
+
From a checkout, the same tool is available as `pnpm run setup` (runs `bun src/install/cli.ts`). The `run` is required — `setup` is a built-in pnpm command, so `pnpm setup` never reaches this script.
219
237
220
238
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.
221
239
@@ -227,7 +245,7 @@ The plugin includes a TUI sidebar widget and an execution dialog for launching p
227
245
228
246
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.
229
247
230
-
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.
248
+
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.
231
249
232
250
### Additional Commands
233
251
@@ -251,7 +269,7 @@ Choose from three execution modes:
251
269
252
270
1.**New session** — Creates a fresh Code session and sends the plan as the initial prompt
253
271
2.**Execute here** — Takes over the current session immediately with the plan
254
-
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)
272
+
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)
255
273
256
274
#### Model Selection
257
275
@@ -353,7 +371,7 @@ After the architect presents a summary, the user chooses an execution mode from
353
371
354
372
-**New session** — Creates a new Code session and sends the plan as the initial prompt.
355
373
-**Execute here** — The code agent takes over the current session immediately with the plan.
356
-
-**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.
374
+
-**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.
357
375
358
376
| Mode | When to choose it |
359
377
|------|-------------------|
@@ -401,7 +419,7 @@ Loop sessions rotate between code and auditor work, so Forge persists per-sessio
401
419
402
420
### Worktree Isolation
403
421
404
-
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).
422
+
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).
405
423
406
424
### Auditor Integration
407
425
@@ -490,7 +508,7 @@ All worktree-based execution paths require a git repository with at least one ro
490
508
When a worktree loop starts with `OPENCODE_EXPERIMENTAL_WORKSPACES=true`, forge:
491
509
492
510
1. Calls `experimental.workspace.create` with `type: "forge"`, `branch: null`, and `extra: { loopName, projectDirectory, workspaceCreatedAt }` to register the workspace through the `forge` adapter
493
-
2. The adapter's `create` hook creates the git worktree (reusing an orphaned branch when possible) and, when configured, provisions the sbx sandbox
511
+
2. The adapter's `create` hook creates the git worktree (reusing an orphaned branch when possible) and, when configured, provisions the msb sandbox
494
512
3. Creates a new Code session pointed at the worktree directory
495
513
4. Calls `experimental.workspace.warp` to bind the session to that workspace
496
514
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
@@ -526,29 +544,29 @@ Worktree loops require a git repository with at least one commit. OpenCode scope
526
544
527
545
## Sandbox
528
546
529
-
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.
547
+
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.
530
548
531
-
See [Sandbox](docs/sandbox.md) for setup, native in-sandbox Docker, network access, environment passthrough, custom bind mounts, large-output handling, and resource defaults.
549
+
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.
532
550
533
551
### Prerequisites
534
552
535
-
- 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).
536
-
- Docker, used only to build the sandbox template.
553
+
- 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). The interactive installer offers to run this for you when `msb` is not on `PATH`.
554
+
- 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).
537
555
- 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).)
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.
567
+
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.
550
568
551
-
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.
569
+
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. The dialog stays open for the duration and shows a live progress bar, the current Docker step, elapsed time, and streamed build output; on failure it keeps the last lines of Docker output so the cause is visible. A first build takes several minutes. Closing the dialog does not cancel the build — it finishes in the background and reports with a toast.
552
570
553
571
Restart OpenCode after changing sandbox configuration.
0 commit comments