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
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:
OrbStack on their own Mac mini — existing hardware, zero marginal cost
k3s on their own Linux — Raspberry Pi, Intel mini PC, or a mixed self-hosted cluster
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.
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.
Why it fits: VM-level isolation for untrusted/AI-generated code, snapshot-based near-instant launch/resume, suspend on idle (compute charges stop; snapshot storage only), dedicated per-VM HTTPS endpoint supporting HTTP/2, gRPC, WebSockets. Available in us-east-1, us-east-2, us-west-2, ap-northeast-1, eu-west-1 (ARM64).
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).
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 rawexectool 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:
Same agent, same
sandbox_*tools — the user picks where the code actually runs, and can switch backends without touching the agent.This directly addresses the trust-model TODO in the README:
With
sandbox_*tools, a bot-scoped token can be restricted to the sandbox tool family only. The bot never gets rawexec/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 --> tcRead 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 thesandbox_*surface.Motivation
tailscale serve+ bearer token. Making it a sandbox gateway is a natural extension.Proposed tools
All tools reuse the semantics already established by the
execfamily (timeout→killpg→ exit 137,structuredContentwith exit/stdout/stderr/duration, job registry with byte-offset incremental polling).sandbox_createsandbox_id. Params:image, optionalname,env,ttl_secs(auto-reap).sandbox_execsandbox_id,command,cwd,env,timeout_secs,max_output_bytes.sandbox_exec_startjob_id.sandbox_exec_pollstdout_since/stderr_sincebyte offsets (same contract asexec_poll).sandbox_exec_cancelKILLdefault /TERM), process-group wide.sandbox_listsandbox_terminateThe 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/TERMINATEDstates onsandbox_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(ororbctl createfor machine-level isolation)sandbox_exec→docker execsandbox_terminate→docker rm -fLater — k3s adapter: their own Linux (Raspberry Pi / Intel mini PC / mixed cluster)
Semantics map 1:1 to Kubernetes primitives:
sandbox_createsleep infinity);ttl_secs→activeDeadlineSecondssandbox_execpods/execsubresource (streaming WebSocket/SPDY, as behindkubectl exec)sandbox_exec_start/pollsandbox_listsandbox_terminateNotes: 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
Auth changes
token:tool-glob), evaluated inAuthPolicyalongside the existing Tailscale login + bearer checks.sandbox_*(and optionallysys_info).Non-goals
sandbox_exec; can be revisited later).Acceptance criteria
sandbox_*can create a sandbox, runsandbox_execwith poll semantics, and terminate it; the same token is denied onexec,mouse,key,osascript.sandbox_exectimeout kills the process group inside the container and reportstimed_out=true, exit 137.ttl_secsreaps forgotten sandboxes;sandbox_listreflects lifecycle states.tailscale serve :8444).References