Skip to content

ADR: sandbox adapter — backend-agnostic sandbox_* tools (OrbStack first; k3s / Lambda MicroVMs later) #1

Description

@chaodu-obk

Summary

Feature name: sandbox adapter.

Add a first-class sandbox_* MCP tool family to oab-instance-mcp so that a remote openab-pty agent (running in a lightweight container, e.g. 0.25 vCPU / 512 MB, wherever it is hosted) can provision isolated tool-execution environments on demand, run commands in them, and tear them down — without ever touching the raw exec tool on the host Mac. The gateway exposes one stable tool schema; a sandbox adapter translates it to a concrete backend — the classic adapter pattern.

The point of the adapter: an OAB agent's tool calls execute on infrastructure the user owns and chooses:

  1. OrbStack on their own Mac mini — existing hardware, zero marginal cost
  2. k3s on their own Linux — Raspberry Pi, Intel mini PC, or a mixed self-hosted cluster
  3. Lambda MicroVMs in their own AWS account — Firecracker isolation, pay-per-second, no hardware

Same agent, same sandbox_* tools — the user picks where the code actually runs, and can switch backends without touching the agent.

Originally filed as openabdev/openab#1547 while this repo was not writable by the bot; moved here.

This directly addresses the trust-model TODO in the README:

Before a sandboxed OAB bot gets this endpoint it needs a tool allowlist and its own token — not built.

With sandbox_* tools, a bot-scoped token can be restricted to the sandbox tool family only. The bot never gets raw exec / mouse / key; its blast radius is the disposable sandbox.

Architecture

flowchart LR
    subgraph brain["Brain — lightweight agent runtime"]
        pty["openab-pty agent<br/>slim container, 0.25 vCPU / 512 MB class<br/><i>mcp.json: instance-mcp gateway</i>"]
    end

    subgraph gw["instance-mcp — sandbox gateway (Mac mini today)"]
        ts["tailscale serve :8444<br/>TLS + Tailscale-User-Login"]
        auth["AuthPolicy<br/>login allow-list AND bearer token<br/><b>new:</b> per-caller tool allowlist<br/>bot token → sandbox_* only"]
        tools["sandbox_create · sandbox_exec<br/>sandbox_exec_start / poll / cancel<br/>sandbox_list · sandbox_terminate"]
        drv{{"sandbox adapter interface<br/>(backend-agnostic)"}}
    end

    subgraph backends["Hands — disposable sandboxes on infrastructure the user owns"]
        orb["<b>orbstack adapter</b> (Phase 1)<br/>their own Mac mini<br/>docker run / exec / rm<br/>zero marginal cost"]
        k3s["<b>k3s adapter</b> (later)<br/>their own Linux<br/>Pi / mini PC cluster<br/>Pod + pods/exec"]
        mvm["<b>microvm adapter</b> (later)<br/>their own AWS account<br/>Lambda MicroVMs · Firecracker<br/>suspend/resume billing"]
    end

    subgraph sb["Sandbox contents"]
        tc["toolchain image<br/>python · node · gh · codex …<br/>per-session workspace state"]
    end

    pty -- "MCP Streamable HTTP<br/>over tailnet" --> ts
    ts --> auth
    auth --> tools
    tools --> drv
    drv --> orb
    drv --> k3s
    drv --> mvm
    orb --> tc
    k3s --> tc
    mvm --> tc
Loading

Read it left to right: the model and its reasoning live in the agent container; the gateway only receives MCP tool calls and translates them through a stable adapter interface; each sandbox is a disposable environment holding the fat toolchain, so the agent image stays slim. The agent runtime is deployment-agnostic — any container platform that can join the tailnet works. Raw host tools (exec, mouse, key, osascript) remain owner-token-only — a bot token can only reach the sandbox_* surface.

Motivation

  • OpenAB agent images are deliberately slim (some coding CLIs are not bundled). Extracting the heavy toolchain into on-demand sandboxes lets the agent runtime shrink to a minimal container while tools run elsewhere.
  • Tool execution stays on infrastructure the user owns — their Mac, their Linux boxes, their AWS account — rather than a third-party sandbox service. No code or credentials leave their trust boundary.
  • instance-mcp already is the "hands" half of the brain/hands split ([Design] OpenAB Mac Agent — cloud brain (k8s) + thin macOS executor over Tailscale openab#1544) and already sits on the tailnet behind tailscale serve + bearer token. Making it a sandbox gateway is a natural extension.
  • The Mac mini + OrbStack backend has zero marginal cost, so the whole architecture can be validated for free before considering paid backends (AWS Lambda MicroVMs, announced 2026-06, is a natural later adapter).

Proposed tools

All tools reuse the semantics already established by the exec family (timeout → killpg → exit 137, structuredContent with exit/stdout/stderr/duration, job registry with byte-offset incremental polling).

Tool Description
sandbox_create Create a sandbox from a named image/template. Returns sandbox_id. Params: image, optional name, env, ttl_secs (auto-reap).
sandbox_exec Run a command to completion inside a sandbox. Params: sandbox_id, command, cwd, env, timeout_secs, max_output_bytes.
sandbox_exec_start Start a background job inside a sandbox; returns job_id.
sandbox_exec_poll Poll a job with stdout_since / stderr_since byte offsets (same contract as exec_poll).
sandbox_exec_cancel Signal a job (KILL default / TERM), process-group wide.
sandbox_list List sandboxes with state, image, created_at, resource usage.
sandbox_terminate Destroy a sandbox and release resources.

The sandbox adapter interface

The tool schema must not leak backend details. Phase 1 implements one adapter; the schema stays stable as adapters are added. Design the lifecycle (PENDING / RUNNING / TERMINATED states on sandbox_create / sandbox_list) from day one, since some backends are async.

Phase 1 — OrbStack adapter: their own Mac mini (this issue)

  • sandbox_create → docker run -d <image> sleep infinity (or orbctl create for machine-level isolation)
  • sandbox_exec → docker exec
  • sandbox_terminate → docker rm -f
  • Zero marginal cost; containers share the OrbStack Linux VM kernel (acceptable for trusted/semi-trusted OpenAB workloads).

Later — k3s adapter: their own Linux (Raspberry Pi / Intel mini PC / mixed cluster)

Semantics map 1:1 to Kubernetes primitives:

Tool k3s implementation
sandbox_create Create a Pod (sleep infinity); ttl_secs → activeDeadlineSeconds
sandbox_exec pods/exec subresource (streaming WebSocket/SPDY, as behind kubectl exec)
sandbox_exec_start / poll nohup + tee to files inside the pod; poll reads byte offsets via exec
sandbox_list List pods by label selector
sandbox_terminate Delete pod

Notes: per-sandbox CPU/RAM quotas, multi-node scheduling, and rescheduling come free from Kubernetes. The gateway only needs a kubeconfig and can run anywhere on the tailnet (more decoupled than the OrbStack adapter, which must run on the Mac). Rewrite scope is small — "MCP server + kube client" in Go/Python; the Mac-specific half (ScreenCaptureKit/CGEvent/TCC) doesn't apply. Multi-arch toolchain images required (docker buildx; Pi = arm64, Intel = amd64). Isolation is shared-kernel, same tier as OrbStack; upgradeable to kata-containers without changing the adapter interface.

Later — MicroVM adapter: their own AWS account

Same schema; the adapter calls run-microvm / JWE auth token / suspend-resume idle policy for Firecracker-level isolation and pay-per-second billing, entirely within the user's own AWS account.

Adapter matrix

sandbox_* schema (stable)
 ├─ orbstack adapter  → their own Mac mini (existing hardware, Phase 1)
 ├─ k3s adapter       → their own Linux: Pi / mini PC / mixed cluster (self-hosted scale-out)
 └─ microvm adapter   → their own AWS account: Lambda MicroVMs (strong isolation + per-second billing)

Auth changes

  • Introduce per-caller tokens with a tool allowlist (e.g. token file entries of the form token:tool-glob), evaluated in AuthPolicy alongside the existing Tailscale login + bearer checks.
  • The existing owner token keeps full access; a new bot token is restricted to sandbox_* (and optionally sys_info).

Non-goals

  • Exposing sandbox network ports to the tailnet (all access is mediated by sandbox_exec; can be revisited later).
  • Multi-host scheduling / autoscaling in Phase 1 — one Mac mini is the deliberate scope.
  • Untrusted third-party code execution — that is the MicroVM-adapter threat model.

Acceptance criteria

  • A bot-scoped token restricted to sandbox_* can create a sandbox, run sandbox_exec with poll semantics, and terminate it; the same token is denied on exec, mouse, key, osascript.
  • sandbox_exec timeout kills the process group inside the container and reports timed_out=true, exit 137.
  • ttl_secs reaps forgotten sandboxes; sandbox_list reflects lifecycle states.
  • Verified end-to-end from a remotely-hosted openab-pty over the tailnet (tailscale serve :8444).

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions