Skip to content

refactor(sandbox): uniformize sandbox/link/dispatch naming#3463

Merged
tlgimenes merged 27 commits into
mainfrom
tlgimenes/decocms-architecture-check
May 25, 2026
Merged

refactor(sandbox): uniformize sandbox/link/dispatch naming#3463
tlgimenes merged 27 commits into
mainfrom
tlgimenes/decocms-architecture-check

Conversation

@tlgimenes
Copy link
Copy Markdown
Contributor

@tlgimenes tlgimenes commented May 25, 2026

What is this contribution about?

Renames the sandbox subsystem around two canonical nouns (Sandbox, SandboxProvider), eliminating the overlapping VM/Runner vocabulary. The three provider kinds become local-docker/cluster/user-desktop (replacing vendor-flavored docker/agent-sandbox/desktop), DispatchTarget is reshaped around a runsIn discriminant with a split error type (user_desktop_link_offline / user_desktop_link_capability_missing), the public MCP tools become SANDBOX_START/SANDBOX_DELETE, the env var becomes STUDIO_SANDBOX_PROVIDER, the daemon route prefix moves from /_decopilot_vm/* to /_sandbox/* with one release of dual-serve compat, and the org-scoped URL surface moves to /api/:org/sandbox/:virtualMcpId/.... Migration 091-sandbox-naming-uniformization sweeps every stored row in one transaction (kind values, vmMapsandboxMap key, inner cell rewrites, and VM_START/VM_DELETE tool-name strings); legacy tolerant readers are removed in the same PR. Full spec and implementation plan committed under docs/superpowers/{specs,plans}/.

How to Test

  1. `bun run check` / `bun run lint` / `bun run fmt:check` — all clean.
  2. `bun test apps/mesh/src apps/mesh/migrations packages/sandbox packages/mesh-sdk` — pre-existing `getSandboxProviderByKind caching` test-pin flake aside, all green.
  3. `bun run dev`, open the studio UI on a clonable agent, trigger `SANDBOX_START`, confirm sandbox provisions and `previewUrl` renders; then `SANDBOX_DELETE` and confirm teardown; send a thread message and confirm dispatch hits `/_sandbox/dispatch`.
  4. Old-daemon compat: start a pre-T11 daemon (only serves `/_decopilot_vm/*`) against an old cluster; this PR's daemon dual-serves both prefixes, so a rolled-out new daemon keeps working with not-yet-rolled-out clusters.
  5. Migration: `bun run --cwd=apps/mesh migrate` against a DB seeded with legacy kind values / `vmMap` keys / `VM_START` references; inspect `connections` rows and `sandbox_runner_state` before/after.

Migration Notes

  • Env var rename (hard cutover): `STUDIO_SANDBOX_RUNNER` → `STUDIO_SANDBOX_PROVIDER`. Helm/deploy values must update atomically with the code rollout; the validator hard-throws on unknown names. Legacy kind values (`docker`/`agent-sandbox`/`desktop`) in the env are also rejected — deploy configs use `local-docker`/`cluster`/`user-desktop`.
  • DB migration 091: runs as part of the deploy. Sweeps `sandbox_runner_state.sandbox_provider_kind` values, renames `virtualmcp.metadata.vmMap` → `metadata.sandboxMap`, rewrites inner kind keys and `sandboxProviderKind` field values, and rewrites `"VM_START"`/`"VM_DELETE"` strings inside stored agent configs. Idempotent; no down migration.
  • Daemon route prefix: cluster speaks only `/_sandbox/` from day one; daemon dual-serves `/_sandbox/` and `/_decopilot_vm/*` for one release. A follow-up PR removes the legacy handlers from the daemon.
  • MCP tool name break: any external client calling `VM_START`/`VM_DELETE` by name will get a 4xx. The studio UI is updated; stored agent configs are rewritten by migration 091.
  • URL surface: `/api/:org/vm/:vmId/...` → `/api/:org/sandbox/:virtualMcpId/...`. The legacy unscoped `/api/vm-events` mount is intentionally NOT touched — it's in the existing `logDeprecatedRoute` cohort with `/api/connections/:id/oauth-token` etc.

Review Checklist

  • PR title is clear and descriptive
  • Changes are tested and working
  • Documentation is updated (spec + plan under `docs/superpowers/`, updated doc comments throughout)
  • No breaking changes — this PR contains breaking changes: MCP tool names, env var, daemon route prefix (mitigated by dual-serve), URL paths. See Migration Notes.

Summary by cubic

Unifies sandbox naming across code, API, and UI: VM → Sandbox, runner → provider, kinds → local-docker/cluster/user-desktop, routes moved to /_sandbox/* and /api/:org/sandbox/:virtualMcpId/:branch/*, and MCP tools renamed to SANDBOX_START/SANDBOX_DELETE. Includes migration (092) to rewrite stored data and drop legacy readers.

  • Refactors

    • Canonical nouns: VM/Runner → Sandbox/SandboxProvider; ensureVmensureSandbox.
    • Provider kinds: dockerlocal-docker, agent-sandboxcluster, desktopuser-desktop; sandboxPreference: "default""cluster-default", "desktop""user-desktop".
    • Tools: VM_START/VM_DELETESANDBOX_START/SANDBOX_DELETE.
    • Routes: daemon /_decopilot_vm/*/_sandbox/* (daemon dual-serves for one release); org API /api/:org/vm/:vmId/.../api/:org/sandbox/:virtualMcpId/:branch/*.
    • Data model: vmMapsandboxMap; VmMapEntrySandboxRecord; vmIdsandboxHandle; sandboxUrlsandboxApiUrl.
    • Dispatch: result is { ok: true, target: { runsIn: "cluster" | "user-desktop" } } or { ok: false, error }; errors use user_desktop_link_* codes.
    • Tests/docs: fix stale migration number and legacy VM_START references.
  • Migration

    • Env var rename (hard cutover): STUDIO_SANDBOX_RUNNERSTUDIO_SANDBOX_PROVIDER.
    • Run 092-sandbox-naming-uniformization: rewrites kind values (incl. legacy remote-useruser-desktop), vmMapsandboxMap, inner keys/fields, and "VM_START"/"VM_DELETE""SANDBOX_START"/"SANDBOX_DELETE"; idempotent, no down.
    • Rollout: cluster speaks /_sandbox/*; daemon dual-serves both prefixes for one release.
    • Update clients/integrations: use new tool names, canonical kind strings, and /api/:org/sandbox/:virtualMcpId/:branch/* URLs.

Written for commit 538b4e7. Summary will update on new commits. Review in cubic

tlgimenes and others added 23 commits May 22, 2026 18:29
Captures the full set of renames anchored on Sandbox/SandboxProvider as the
canonical nouns: collapses VM/Runner synonyms, replaces vendor-flavored kinds
(docker/agent-sandbox/desktop) with location/role-flavored ones
(local-docker/cluster/user-desktop), restructures DispatchTarget around
`runsIn`, splits dispatch errors into their own type with user_desktop_link_*
codes, renames daemon routes from /_decopilot_vm/* to /_sandbox/* with one
release of dual-serve, and renames the public MCP tools VM_START/VM_DELETE
→ SANDBOX_START/SANDBOX_DELETE. Includes a single Kysely migration that
sweeps stored kind values, the vmMap→sandboxMap key rename, and stored
tool-name references in agent configs; legacy tolerant readers are dropped
in the same PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the 17-task implementation plan for the sandbox naming spec, and
expands the spec's daemon-routes section to enumerate the full
/_decopilot_vm/* surface (config/idle/events/read/write/edit/grep/glob/
bash/write_from_url/upload_to_url, not just dispatch/runs) so the
dual-serve compat covers the whole prefix.

Tasks are ordered to leave the codebase green after each task. The
canonical-noun foundation (types, enum, field names) lands first; the
DB migration (Task 14) goes last so it deploys alongside code that
reads/writes the new shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cluster/user-desktop

Renames the three SandboxProviderKind enum values throughout the codebase
to use clearer, role-descriptive names:
  - "docker" → "local-docker"
  - "agent-sandbox" → "cluster"
  - "desktop" → "user-desktop"

Updates the env var default (STUDIO_SANDBOX_RUNNER, name unchanged), the
RUNNER_KIND constants in each provider, the resolver/lifecycle dispatch
sites, the VM_START/VM_DELETE schemas, tests, and UI references. The
sandboxPreference / DispatchTarget.sandbox string-literal unions are
intentionally left alone (renamed in a later task).

Mesh-SDK's tolerant readers (parseVmMapEntry, parseBranchMap) accept both
the canonical kinds and the legacy ones ("docker"/"agent-sandbox"/"desktop"
plus pre-removal "freestyle"/"host") and normalize them to canonical on
read, so on-disk vmMap entries written before this rename keep parsing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two describe() docstrings still referenced the legacy "docker"/
"agent-sandbox"/"desktop" kind names after the value rename in 10dd34f.
Sync them to "local-docker"/"cluster"/"user-desktop" so the schema docs
match the storage-types update from the same commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…w kinds

T1+T2 follow-up addressing code-quality review findings:
- Update tests/multi-pod/docker-compose.yml, deploy/helm/sandbox-env/README.md,
  packages/sandbox/README.md to use canonical kind values; the env validator
  now hard-rejects legacy values.
- Widen input Zod schemas (decopilot/schemas.ts, vm/start.ts) to accept legacy
  kind values and normalize at the boundary, so Studio tabs in flight during
  rollout don't get 4xx errors.
- Export the legacy->canonical normalizer from mesh-sdk and reuse it in
  vm-events-handler and vm/stop instead of inlining the cascade.
- Update stale describe-string and block comment; add TODO(task-15) pointer
  next to the widened tolerant-reader union.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…andle

Tolerant reader accepts both `vmId` (legacy) and `sandboxHandle` (canonical)
on read; writers emit only the new name. The data migration in Task 14
sweeps existing rows; Task 15 removes the tolerant key after the rolling
deploy window.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
readSandboxMap prefers the new `sandboxMap` key and falls back to `vmMap`
for rows that the data migration (T14) hasn't rewritten yet.
setSandboxMapEntry writes under the new key only and strips the legacy
key from the returned metadata blob. T15 will drop the fallback after
the rolling deploy window.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r type

resolveDispatchTarget now returns { ok: true, target } | { ok: false, error }.
Success shape discriminates on `runsIn: "cluster" | "user-desktop"`; the
sandbox field uses the canonical SandboxProviderKind. Error codes renamed to
user_desktop_link_offline / user_desktop_link_capability_missing for
clarity. Callers updated to the new pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The discriminated union returned by resolveDispatchTarget (T6) makes the
error case impossible past the prepare boundary, so the class has zero
importers. Renamed code strings live on DispatchError.kind instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_DELETE

Hard cutover for the public MCP tool names. The DB migration (T14) will
rewrite stored references in virtualmcp.metadata; old agent configs
holding "VM_START" tool-name strings get swept atomically with the
deploy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the ambiguity between sandboxUrl (control-plane URL where the
sandbox's HTTP API lives) and previewUrl (where the user's dev server
serves their app). LinkEntry.tunnelUrl and Sandbox.previewUrl are
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…IDER

Hard cutover. The env-var renames the "runner" noun to match the
canonical "provider" terminology established elsewhere
(SandboxProvider, SandboxProviderKind). Helm + deploy bump is atomic
with the code rollout — no compat read of the old name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…* (dual-serve)

The cluster speaks only the new /_sandbox/* prefix. The daemon registers
each route under both prefixes for one release window so daemons that
haven't auto-updated keep serving while old clusters roll out. The
housekeeper script tries the new idle path first and falls back to the
legacy path. The next-next release will remove the legacy handlers from
the daemon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ider.ts

The file implements the desktop-side provider for the user-desktop kind;
the new name is self-describing within the link-daemon directory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ents/sandbox; vm-* route files

Aligns directory and file names with the canonical Sandbox vocabulary.
Also renames stop.ts → delete.ts to match the SANDBOX_DELETE tool name,
and updates the related web hooks/symbols (useVmStart → useSandboxStart,
VmEventsContext → SandboxEventsContext, etc.) inside the renamed subtree.

The vmId URL parameter in sandbox-proxy.ts is intentionally left alone
to keep the change scoped — it's a routing-layer detail that needs a
coordinated frontend update. The api/routes/vm-events.ts parent file
is also left unrenamed (not listed in the task) and will be addressed
in the docs/comments sweep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ulary

Single-transaction migration that:
- Rewrites sandbox_runner_state.sandbox_provider_kind values (docker →
  local-docker, agent-sandbox → cluster, desktop → user-desktop, plus
  legacy remote-user → user-desktop).
- Renames connections.metadata.vmMap → metadata.sandboxMap.
- Rewrites inner kind keys and the sandboxProviderKind field inside each
  sandboxMap cell with the same mapping; drops legacy host/freestyle
  cells.
- Rewrites stored tool-name strings "VM_START"/"VM_DELETE" →
  "SANDBOX_START"/"SANDBOX_DELETE" anywhere they appear in agent
  metadata.

All steps idempotent. No down migration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migration 091 rewrote every persisted legacy value (docker/agent-sandbox/
desktop/remote-user/host/freestyle kind values; vmMap key; vmId field;
VM_START/VM_DELETE tool-name strings). The tolerant readers in mesh-sdk
and sandbox-map are now unreachable in steady state — drop them and the
normalizer helper, narrow the kind union to the canonical 3 values, and
delete the legacy-fallback tests. Input Zod schemas tighten back to
canonical-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sweeps VM → Sandbox, vmMap → sandboxMap, ensureVm → ensureSandbox,
decopilot_vm → sandbox in doc comments and READMEs. Migration files,
spec/plan docs, and test fixture strings are intentionally preserved as
historical references.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final stray VM→Sandbox name. The function parses a SandboxRecord; the
old name was a holdover from the VmMapEntry type that T3 renamed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final stray "runner" noun in the env validator. Aligns the private const
with the rest of the canonical vocabulary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ocab

"default" → "cluster-default" (use whichever sandbox kind STUDIO_SANDBOX_PROVIDER
resolves to — could be local-docker, cluster, or user-desktop). "desktop" →
"user-desktop" (matches SandboxProviderKind). dispatch-run.ts no longer
needs to translate at the boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sandbox/:virtualMcpId

The route mount and param name both update to canonical vocab. The param's
value is the virtual MCP ID — the old name `:vmId` was misleading (handler
already aliased it to `virtualMcpId` internally). createVmRoutes →
createSandboxRoutes. Frontend SSE URL builder updates in lockstep.

Legacy unscoped /api/vm-events mount is intentionally NOT touched here —
it's part of the separate deprecation-window cohort with
logDeprecatedRoute middleware.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Benchmark

Should we run the Virtual MCP strategy benchmark for this PR?

React with 👍 to run the benchmark.

Reaction Action
👍 Run quick benchmark (10 & 128 tools)

Benchmark will run on the next push after you react.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

Release Options

Suggested: Patch (2.346.5) — based on refactor: prefix

React with an emoji to override the release type:

Reaction Type Next Version
👍 Prerelease 2.346.5-alpha.1
🎉 Patch 2.346.5
❤️ Minor 2.347.0
🚀 Major 3.0.0

Current version: 2.346.4

Note: If multiple reactions exist, the smallest bump wins. If no reactions, the suggested bump is used (default: patch).

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 160 files

Note: This PR contains a large number of files. cubic only reviews up to 100 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.

Re-trigger cubic

tlgimenes and others added 4 commits May 25, 2026 10:30
…hitecture-check

# Conflicts:
#	apps/mesh/migrations/index.ts
#	apps/mesh/src/web/components/sandbox/preview/preview.tsx
The "picks the most-recently-touched sandboxMap branch" test used the
legacy `freestyle` provider kind, which normalizeSandboxMap now silently
drops, causing the handler to fall back to a generated branch name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ests

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tlgimenes tlgimenes merged commit 44e2ed4 into main May 25, 2026
20 checks passed
@tlgimenes tlgimenes deleted the tlgimenes/decocms-architecture-check branch May 25, 2026 13:50
tlgimenes added a commit that referenced this pull request May 25, 2026
…3468)

Releases a new studio-sandbox image that includes the dual-serve daemon
routes from #3463 (/_sandbox/* + legacy /_decopilot_vm/*), and bumps the
sandbox-env chart's image.tag so deployed warm-pool sandboxes pick up the
new image. Without this, post-rename mesh callers hit /_sandbox/config on
0.5.4 daemons that only know the legacy prefix and get a 503 "No dev
server" HTML from the fallback proxy.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant